Skip to main content

Notifications (Twilio, SendGrid, WhatsApp)

Nexa delivers notifications to passengers and operators across multiple channels, with delivery telemetry from each. The notifications domain is the single outbound communication surface for the platform — every other domain enqueues a notification request; the notifications domain decides how it gets delivered.

Channel matrix

ChannelVendorTenant scopeDelivery receiptOpen / read2-way
SMSTwilioPer-tenant phone numbersYes (status callback)NoOptional
EmailSendGridShared platformYes (Event Webhook)Yes (open + click)No
WhatsAppMeta WhatsApp BusinessPer-tenant phone numbersYes (status webhook)Yes (read receipts)Yes
Web PushWeb Push (VAPID)Per-tenant subscriptionServer-side delivery onlyNoNo
Airline app deep-linkPer-airline custom adapterPer-tenantPer integrationPer integrationPer integration

The first three are the primary channels; Web Push is the PWA-resident channel for passengers who have the webapp installed; the airline-app deep-link is a per-airline custom adapter when an airline has its own consumer app and wants Nexa notifications to surface there.

Architecture

Triggers

A notification is dispatched on a trigger event. Examples:

TriggerChannel defaultRecipient
case.disruption.detected (auto)SMS + WhatsApp + EmailAffected passengers
subcase.offer-readySMS + WhatsApp + Email (with magic-link)Lead passenger of the group
voucher.issuedEmail (with PDF voucher)Lead passenger
wallet.card-issuedSMS + Email + PushLead passenger
wallet.card-loaded (scheduled drop)Push (preferred) + SMS fallbackLead passenger
transport.dispatchedSMS + PushLead passenger
subcase.failedOperator UI + email to OPS_SUPERVISOROperators

Template selection

A notification has three layers:

  1. Trigger event — what happened.
  2. Recipient segment — passenger tier, language, opt-ins, special-needs flags.
  3. Channel — picked by per-tenant policy + recipient preference + fallback chain.

The template store carries variants per (trigger, segment, channel, language) tuple. Selection logic:

template = templateStore.select({
trigger: "subcase.offer-ready",
segment: { tier: "BUSINESS", language: "es" },
channel: "whatsapp"
})

If a specific variant doesn't exist, the resolver falls back: tier-specific → tenant-default → platform-default. Language fallback: requested → es (Latin America default) → en.

Special-content notifications

Some notifications need extra content beyond the standard offer:

  • Baggage instructions — when the disruption requires baggage re-collection at a different airport, the template includes the instructions and any reference numbers.
  • Special-needs-aware messaging — for passengers with WHEELCHAIR_REQUEST, INFANT, MEDICAL flags, the template includes specific accommodation guidance.

These rules live in the template-selection layer; the content variants live in the template store.

Channel routing

Per-tenant policy declares the default channel chain per trigger:

case.disruption.detected → [whatsapp, sms, email]   // try whatsapp first; fall back if undeliverable
subcase.offer-ready → [sms, whatsapp, email]
voucher.issued → [email] // email-only; voucher is HTML
wallet.card-loaded → [push, sms] // push if PWA installed; else SMS

Per-passenger preferences override the default chain — a passenger who unsubscribed from SMS receives email-only.

The dispatcher attempts the first channel; on confirmed delivery (or after the channel's grace period), it considers the notification delivered. On undeliverability (bounce, opted-out, no number on file), it falls back to the next channel.

Idempotency

Every dispatch carries a notification_urn. Replays of the same trigger (e.g., from at-least-once event delivery) find the existing dispatch and short-circuit — no duplicate sends.

Delivery telemetry

Every channel adapter exposes inbound webhooks for delivery state:

  • Twiliodelivered, failed, undelivered, replied.
  • SendGriddelivered, bounce, dropped, open, click, unsubscribe.
  • WhatsAppsent, delivered, read, failed.
  • Web Push — server-side delivery confirmation only (no read receipt).

Inbound webhooks are HMAC-validated, normalized into the canonical NotificationDeliveryEvent shape, and persisted to the per-tenant notifications store. The aggregated event stream is published on the workflow bus for downstream consumers (analytics, audit, partner subscribers).

The operator UI surfaces the delivery state per-passenger ("Delivered to WhatsApp 3m ago, read 2m ago, accepted offer 1m ago") so operators can see the full chain.

Bounce / opt-out handling

When a delivery webhook reports a hard bounce or opt-out:

  1. The recipient's contact preferences for that channel are flagged.
  2. Future dispatches for that recipient skip the flagged channel.
  3. The next available channel in the routing chain is used.

If every channel is unavailable, the operator UI surfaces a "passenger unreachable" warning on the case, prompting the operator to call the number directly or refer to airport-side communication.

Tenant-managed credentials

VendorCredentialsNotes
TwilioPer-tenantEach airline has its own dedicated phone number(s) for branding and deliverability.
SendGridNexa-managedBundled in the subscription. Sender domain is notify.<airline>.nexastudio.io for per-tenant branding.
Meta WhatsApp BusinessPer-tenantEach airline has its own WhatsApp Business phone number; templates need Meta-side approval per airline.
Web Push (VAPID)Per-tenantVAPID keys are per-tenant; subscriptions live in the per-tenant DB.

SLA & latency

ChannelDetection-to-first-message p95
SMS (Twilio)< 30 s
WhatsApp< 60 s
Email (SendGrid)< 60 s
Push< 30 s

Latency is measured from the subcase.offer-ready event (or equivalent trigger) to the channel-side delivery confirmation. Vendor-side latency outside Nexa's control is excluded.

Failure modes

FailureAction
Twilio 5xxRetry with backoff; fall back to next channel after 3 attempts.
SendGrid bounceMark email invalid; fall back to next channel.
WhatsApp template rejected by MetaAlert ops; cannot recover automatically.
Webhook signature invalidReject with 401; vendor retries.
Vendor outage > 5 minCircuit breaker opens for the channel; operator UI surfaces partial delivery.

Compliance

  • All vendors are data sub-processors under Nexa's tenant DPAs.
  • Phone numbers and emails sent to vendors are minimum-necessary.
  • Opt-out / unsubscribe handling is mandatory per channel-specific regulation (CAN-SPAM, GDPR, LGPD).
  • Delivery logs retain the message body for 30 days for support purposes; redacted summaries are retained for 12 months for analytics.

Onboarding checklist

For a new tenant:

  • Tenant procures Twilio account + dedicated phone number(s).
  • Tenant procures Meta WhatsApp Business account + phone number; Meta-approves notification templates.
  • Nexa configures SendGrid sender domain notify.<airline>.nexastudio.io.
  • Webhook endpoints registered with each vendor (per-tenant).
  • Default channel routing configured per tenant policy.
  • First end-to-end notification validated against sandbox case.
Was this helpful?