MessageBird SMS
SMS messaging via the Bird API (the provider formerly named MessageBird). Used for phone verification, 2FA codes, and document-obtaining reminders/escalations.
Architecture
| Component | Path |
|---|---|
| SMS config + parsing | backend/src/lib/sms/config.ts (env loading, E.164 catch-all parsing) |
| SMS client + retry | backend/src/lib/sms/index.ts, backend/src/lib/sms/retry.ts |
| SMS service | backend/src/lib/services/sms.ts |
| Intent-based notification service | backend/src/lib/notifications/notification-service.ts (+ channel-resolver.ts, renderers.ts) |
| Queue processors | backend/src/lib/jobs/processors/notifications.ts, document-reminder.ts |
Configuration
| Variable | Purpose |
|---|---|
BIRD_API_KEY | Bird API authentication |
BIRD_ORIGINATOR | Sender ID or number (default: Renewa, max 11 chars alphanumeric) |
SMS_CATCH_ALL_PHONE_NUMBERS | Comma-separated redirect targets (max 10, normalized to E.164) |
SMS_ALLOW_REAL_DELIVERY | true enables delivery to actual recipients |
SMS_MAX_RETRIES / SMS_RETRY_INITIAL_DELAY_MS / SMS_RETRY_MAX_DELAY_MS | Optional retry tuning (default: 3 retries, exponential backoff with jitter) |
Secrets live in Infisical (EU) and sync natively to Fly.io (not GitHub Environment Secrets). See Deployment Pipeline.
SMS Types
Rendered by intent in backend/src/lib/notifications/renderers.ts:
| Intent | Trigger | Related Feature |
|---|---|---|
phone_verification | User verifies a phone number | Authentication |
2fa_code | Login second factor (SMS or email channel) | Authentication |
document_reminder | Automated document-obtaining follow-up | Document Obtaining, Portal |
| Escalations | Reminder escalation steps | Document Obtaining |
Delivery Safety
Delivery is flag-gated, not environment-gated: unless SMS_ALLOW_REAL_DELIVERY=true, all SMS are redirected to the SMS_CATCH_ALL_PHONE_NUMBERS list (resolveRecipients in lib/sms/config.ts).
| Condition | Behavior |
|---|---|
SMS_ALLOW_REAL_DELIVERY=true | Send to actual recipients (production) |
| Catch-all numbers configured | All SMS redirected to catch-all numbers |
| No catch-all numbers, no real delivery | Mock mode (no API key configured → service reports unconfigured) |
There are no hardcoded default catch-all numbers — every environment must configure them explicitly.
Background Processing
SMS messages are sent asynchronously via the BullMQ notification queue:
- A caller invokes the intent-based
NotificationServiceImpl.notify(intent, recipient, payload); channel resolution + rendering produce anSmsJobDatajob - Background Jobs processor (
processors/notifications.ts,channel === 'sms') sends viasmsService.sendSms() - Transient failures are retried with exponential backoff + jitter (
lib/sms/retry.ts); failures are logged
This pattern is shared with Resend Email — both channels flow through the same notification queue and processor.
Phone Number Handling
Phone numbers are normalized to E.164 (+49123456789) via libphonenumber-js (normalizePhoneNumberE164, DE as default country). Invalid numbers are rejected with a descriptive error; failures surface in Sentry.
Related
- Notifications — Notification dispatch architecture
- Authentication — Phone verification and 2FA codes
- Portal — Portal notification SMS
- Document Obtaining — Reminder and escalation SMS
- Background Jobs — Async SMS processing queue
- External Integrations — All third-party integrations
- Sentry — Delivery failure tracking