Provider independence (swap who carries your email, texts and payments)

Every outbound email, text and card payment is routed through a per-account provider chain stored in the database, so changing who delivers your messages is a config flip rather than a rebuild.

What it is

App code never talks to Twilio, Mailgun, Resend, GoHighLevel or Stripe directly. It calls a port (sendCustomerEmail / sendCustomerSms / paymentsFor), which resolves that account's adapter chain from org_provider_bindings and walks it in priority order. Adapters are the only files allowed to speak a vendor's wire protocol. An account with no rows falls back to a platform default chain, so nothing has to be configured before it works.

Also called: provider registry · ports and adapters · switch SMS provider · not locked into one vendor · org provider bindings

See it
Provider independence (swap who carries your email, texts and payments)
Area
Integrations
Group
Provider architecture
System
Provider Independence
Solves
3 named problems
01A send arrives at the port facade with an org id.
02The registry reads org_provider_bindings for that org and capability (email / sms / voice / payments), filtered to active rows, ordered by priority, cached 60 seconds.
03Secrets stored as enc:v1: strings are decrypted in the registry so adapters receive ready-to-use values.
A lane diagram: one message entering a chain of provider cards (Twilio → GoHighLevel), the first one lighting green, with a send_log row appearing beneath naming the provider. Settings surface is src/components/settings/CommunicationsTab.tsx. Sample data — no customer information appears here.
How it works
  1. 1A send arrives at the port facade with an org id.
  2. 2The registry reads org_provider_bindings for that org and capability (email / sms / voice / payments), filtered to active rows, ordered by priority, cached 60 seconds.
  3. 3Secrets stored as enc:v1: strings are decrypted in the registry so adapters receive ready-to-use values.
  4. 4Each adapter in the chain is tried until one returns 'sent'; 'skipped' means not applicable, 'failed' means it tried and errored.
  5. 5The result is written to send_log with the provider that actually delivered it; a retryable failure lands in message_outbox.
Why we built it

The roadmap comment is explicit that adopting the port had to be a zero-behaviour-change deploy: 'An org with NO rows gets the PLATFORM DEFAULT chain below — which reproduces the pre-ports routing exactly... That makes adopting the port a zero-behavior-change deploy, and makes provider cutover a per-tenant config flip.' The example in the file spells out the payoff: a two-row INSERT moves a tenant to Mailgun, and 'rollback = flip status/priority back. No deploy.' Before this, who carried a builder's messages was a property of the code, so every vendor decision was permanent.

The problem
  • Vendor choice was hard-coded, so switching providers meant a code change and a deploy.
  • A single provider outage stopped all customer messaging with no fallback.
  • Different tenants needed different stacks (one insisted on GoHighLevel, everyone else on in-house) with no way to express that.
Sound familiar?
What you get
A tenant can be moved between providers without a deploy and rolled back the same way.
A failed provider falls through to the next one mid-send instead of dropping the message.
New providers are added by writing one adapter and one catalogue entry.
What's inside
Automatic fallback when a provider fails
If the primary provider refuses or times out, the message is immediately retried through the next provider in the account's chain instead of failing.
Sensible defaults for a brand-new account
An account whose provider rows are missing still sends, through a default chain that reflects current policy rather than legacy behaviour.
Encrypted provider credentials, decrypted at the boundary
Provider secrets live encrypted in the binding row and are decrypted once, in the registry, so adapters receive usable strings and secrets never sit in plaintext.
One switch: in-house or GoHighLevel
A single per-account toggle rewrites the whole messaging stack — Twilio and Mailgun, or GoHighLevel — and can be flipped back at any time.
Provider picker that refuses a switch that would break you
Each provider declares what must be true before it can carry a channel, and the server checks that against live account state before writing the change.
A new account is wired for sending the moment it exists
Every account-creation path seeds a full email chain on a neutral shared sending domain and provisions the builder's own Twilio subaccount at birth.
Which provider actually delivered this
Every send writes a row naming the provider that carried it, its provider message id, the outcome, and the billable units.
Retry queue for sends that never reached the provider
A send that failed at the network level is persisted and replayed on a backoff, while a send the provider actually rejected is never retried.

See it on your own jobs

Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.

or keep browsing features →