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

ComponentPath
SMS config + parsingbackend/src/lib/sms/config.ts (env loading, E.164 catch-all parsing)
SMS client + retrybackend/src/lib/sms/index.ts, backend/src/lib/sms/retry.ts
SMS servicebackend/src/lib/services/sms.ts
Intent-based notification servicebackend/src/lib/notifications/notification-service.ts (+ channel-resolver.ts, renderers.ts)
Queue processorsbackend/src/lib/jobs/processors/notifications.ts, document-reminder.ts

Configuration

VariablePurpose
BIRD_API_KEYBird API authentication
BIRD_ORIGINATORSender ID or number (default: Renewa, max 11 chars alphanumeric)
SMS_CATCH_ALL_PHONE_NUMBERSComma-separated redirect targets (max 10, normalized to E.164)
SMS_ALLOW_REAL_DELIVERYtrue enables delivery to actual recipients
SMS_MAX_RETRIES / SMS_RETRY_INITIAL_DELAY_MS / SMS_RETRY_MAX_DELAY_MSOptional 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:

IntentTriggerRelated Feature
phone_verificationUser verifies a phone numberAuthentication
2fa_codeLogin second factor (SMS or email channel)Authentication
document_reminderAutomated document-obtaining follow-upDocument Obtaining, Portal
EscalationsReminder escalation stepsDocument 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).

ConditionBehavior
SMS_ALLOW_REAL_DELIVERY=trueSend to actual recipients (production)
Catch-all numbers configuredAll SMS redirected to catch-all numbers
No catch-all numbers, no real deliveryMock 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:

  1. A caller invokes the intent-based NotificationServiceImpl.notify(intent, recipient, payload); channel resolution + rendering produce an SmsJobData job
  2. Background Jobs processor (processors/notifications.ts, channel === 'sms') sends via smsService.sendSms()
  3. 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.