Skip to content

feat(sms): MessagingProvider abstraction + BYO Twilio/Telnyx provider choice - #205

Merged
important-new merged 15 commits into
InspectorHub:mainfrom
important-new:feat/sms-provider-interface
Jun 26, 2026
Merged

feat(sms): MessagingProvider abstraction + BYO Twilio/Telnyx provider choice#205
important-new merged 15 commits into
InspectorHub:mainfrom
important-new:feat/sms-provider-interface

Conversation

@important-new

@important-new important-new commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces a provider abstraction for SMS so self-hosters/tenants can bring their own provider (Twilio or Telnyx), and surfaces provider + credential selection in Settings → Communication. This is the open-source BYO + provider-interface scope of #196. (The Track L SMS engine — send, consent, opt-in/STOP/HELP, templates, metering — already shipped; this PR adds the abstraction layer + a second adapter.)

What's included

  • MessagingProvider interface + thin fetch adapters: TwilioClient (extracted from the existing basic-auth path) and a new TelnyxProvider (Telnyx v2 Messages API). No provider SDK.
  • resolveProvider(byoProvider, creds) selection; the live send path (test-send + automation) routes through it. Twilio behavior is byte-for-byte unchanged.
  • tenant_configs.sms_byo_provider column + messaging_compliance table (schema-first migration; clean ADD COLUMN, no table rebuild).
  • BYO verification-status read-back (GET /api/manager/sms/compliance) + a status pill in Settings.
  • Sender-mode selector; platform is reserved for first-party (rejected as a tenant choice; standalone forced to BYO).
  • Settings: provider selector (Twilio/Telnyx) + dynamic provider-namespaced credential fields, stored in the existing encrypted secrets envelope.
  • Self-host SMS-compliance docs (docs/sms-compliance.md).

Out of scope

Platform-provisioned / managed sending (where the platform handles carrier onboarding on a tenant's behalf) is intentionally not in this PR — self-host + BYO is the supported path here. The managed_* sender modes are selectable placeholders without a live send path.

Deferred follow-up

Telnyx inbound STOP/HELP (Ed25519 webhook verification) — outbound ships now; validateInboundSignature is a documented stub.

Verification

  • test:unit (2190) + test:web (593) + full type-check + db:check all green.
  • Local Chrome E2E on real workerd + D1: provider selector renders, dynamic field switching (Twilio 3 ↔ Telnyx 2 fields), save persists sms_byo_provider='telnyx' + encrypted creds, reload reads back the choice.

Refs #196

🤖 Generated with Claude Code

important-new and others added 14 commits June 26, 2026 00:11
…nt adapter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-dedicated)

- server: expand TenantConfigPatchSchema smsMode enum to own|managed_shared|managed_dedicated
  (platform excluded from tenant input); PATCH handler rejects 'platform' on SaaS (zod
  enum gate + runtime guard), coerces any mode to 'own' on standalone
- ui: replace two-button platform/own toggle with three-option radio selector in
  SmsDeliveryPanel (own=BYO, managed_shared=default/included, managed_dedicated=disabled
  paid-upgrade affordance); standalone shows only BYO notice unchanged
- action: settings-communication save-sms-config passes through three valid modes
  (never submits 'platform'); legacy stored 'platform' value renders as managed_shared
- tests: 5 new cases in sms-api.spec.ts covering SaaS rejection of platform, SaaS
  acceptance of own/managed_shared/managed_dedicated, standalone coercion to own
- baseline: bump file-size-baseline for the two files that grew (reviewed decision)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6hYGDtjhEfMXwWDz4om21
…aseline

The provider-aware automation refactor shifted line numbers, staling the
line-keyed tenant-scoping baseline. Properly scope the three automationLogs
status updates by tenant (inspection.tenantId) — a security improvement over
baselining — and re-snapshot the baseline for the incidental line-shifts of
pre-existing accepted queries in delivery.ts / api/sms.ts.
@important-new
important-new merged commit b3e23d0 into InspectorHub:main Jun 26, 2026
4 checks passed
@important-new
important-new deleted the feat/sms-provider-interface branch June 26, 2026 01:57
important-new added a commit that referenced this pull request Jun 26, 2026
…re (#207)

The Communication settings Delivery panels predated the multi-provider
abstractions (#205 SMS Twilio/Telnyx, #206 email Resend/SendGrid/Postmark/
Mailgun) and still hardcoded Resend/Twilio in their mode labels, hints, and
"configured" guardrails. With a non-default provider selected this produced
wrong copy and a false "no Resend key — emails will fail" banner even when the
chosen provider (e.g. SendGrid) was fully configured.

- Email delivery: mode toggle "My own Resend" → "My own provider"; own-mode
  guardrail + status + sender-domain hint now reflect email_byo_provider
  (Resend/SendGrid/Postmark/Mailgun). The own-mode "configured" check keys off
  the selected provider's credentials (Mailgun needs key + domain), not just
  RESEND_API_KEY. Platform-mode path is unchanged.
- SMS delivery: intro/self-host/effective-source copy generalized off the
  Twilio-only wording to the selected provider (Twilio/Telnyx); the toll-free
  compliance block is gated to Twilio (Telnyx has a different flow).
- Extracted ownEmailProviderConfigured() to app/lib/email-provider-config.ts
  (pure, unit-tested) — the masked-secret "" = not-configured contract.

Client-only; no server/schema change. Full local verify green
(lint/type-check/db:check/unit 2256/web 611/build).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant