SMS delivery operations
Delphi sends SMS messages through seven.io. A flow action enqueues an SMS_SEND job; the
Tasker worker performs the authoritative feature and quota checks,
dispatches the message, and records the provider result and usage.
Delivery path
- A voice or browser flow invokes
sendSMSwith a recipient, message text, and optional sender. - The invoking application validates the request and enqueues one
SMS_SENDjob. - Tasker checks the
smsfeature flag, estimates the billable message parts, and verifies the team's current SMS allowance and spending cap. - Tasker sends the message to
https://gateway.seven.io/api/smsthrough the deployment's configured HTTPS proxy. - After seven.io accepts the message, Tasker saves a durable delivery checkpoint before updating subscription usage. A retry resumes accounting from that checkpoint instead of sending again.
Configuration
| Variable | Source | Consumer | Required when SMS is enabled |
|---|---|---|---|
FEATURE_SMS | Feature flag | Platform services | Set to true. |
SMS_API_KEY | Secrets Manager | Tasker worker | Yes. |
These are the only SMS-specific deployment variables. The provider name is deliberately absent
from the variable names so the deployment contract remains provider-neutral. There is no
provider-specific sender variable: a flow may supply from, otherwise Delphi uses its validated
default sender.
Before enabling SMS:
- create and fund the seven.io account;
- store its API key as
SMS_API_KEYin the Ops secret; - allow HTTPS egress to
gateway.seven.io:443through Squid; and - enable the
smsfeature flag, then restart the affected application and Ops containers so they re-read their environment.
If SMS is disabled, the platform does not require SMS_API_KEY. Upstream services skip new
dispatches and Tasker refuses SMS jobs that were already queued.
Multipart messages and limits
Quota enforcement is based on estimated parts, not action calls. Before provider dispatch, Tasker applies the following encoding limits:
| Encoding | Single part | Each concatenated part |
|---|---|---|
| GSM-7 | 160 septets | 153 septets |
| UCS-2 | 70 units | 67 units |
GSM-7 extension-table characters consume two septets. After acceptance, the provider-reported part count is recorded as the actual usage. This prevents a multipart message from bypassing the team's allowance or hard spending cap.
Team administrators can inspect current SMS usage and change applicable limits in My Subscription. The Dashboard shows current usage. Usage and overage notifications link to both surfaces; when subscription management is unavailable, the unavailable action is hidden.
Retry and rejection behavior
- Tasker passes the job ID to seven.io as correlation metadata and disables seven.io's account-wide repeated-content lock. Distinct jobs with the same recipient and text remain valid.
- Network failures, timeouts, HTTP
429, and server errors are retryable through the normal Tasker job policy. - Permanent provider rejections complete as rejected and are not retried.
- A provider acceptance checkpoint is scoped to the original Tasker job. Do not create a new job merely because usage accounting failed; inspect the original job result first to avoid a second paid delivery.
Automated regression checks
From the application repository root:
pnpm --filter @delphi/notification exec vitest run src/sms/segmentation.test.ts src/transport/sms.test.ts
pnpm --filter @delphi/tasker exec vitest run src/services/handlers/__tests__/sms-send.test.ts \
src/services/worker/__tests__/sms-worker.integration.test.ts
The first command covers encoding, multipart estimation, seven.io request/response handling, and provider errors. The second covers quota enforcement, durable retry checkpoints, provider rejections, and usage accounting.
Controlled paid end-to-end test
The repository contains a local-only setup script that creates and publishes a dedicated Seven SMS E2E app without changing an existing phone or SIP endpoint.
-
Confirm
FEATURE_SMS=true,SMS_API_KEYis populated, the test team has SMS enabled, and its subscription has available SMS usage or permitted overage. -
From the application repository root, run:
NODE_ENV=development ALLOW_PAID_SMS_E2E=true \SMS_E2E_TEAM_ID=<team-id> SMS_E2E_SOURCE_APP_ID=<source-app-id> \pnpm with-env pnpm --filter @delphi/api exec tsx scripts/setup-seven-sms-e2e-flow.ts -
Check the command's
smsReadyoutput.featureEnabled,apiKeyConfigured, andteamEnabledmust betrue; also review the subscription status and usage values. -
Sign in as a team administrator, open Apps → Seven SMS E2E, and start its browser test call.
-
Give one real recipient in E.164 format, repeat the complete number, and explicitly authorize one paid SMS. Do not repeat the authorization or trigger the action twice.
-
Confirm the flow reports one queued job ID, the recipient receives the uniquely timestamped message, and the Tasker job completes with
success,messageId,providerCode,parts, and price fields. -
Verify the SMS count in Dashboard and My Subscription advances by the reported number of parts.
Use a recipient that has consented to the test. Keep ALLOW_PAID_SMS_E2E scoped to the setup command
and remove the temporary app after testing if it is no longer needed.
Troubleshooting
| Symptom | Check |
|---|---|
| Job says SMS is disabled | FEATURE_SMS in the affected services and whether Tasker restarted after the flag change. |
| Job fails before provider dispatch | Team SMS setting, estimated parts, included allowance, spending cap, and subscription status. |
| Provider authentication is rejected | SMS_API_KEY in the Ops secret; re-run Ops init and restart Tasker after rotation. |
| Timeout or network error | Tasker proxy variables and Squid access to gateway.seven.io:443. |
| SMS arrived but usage update was retried | Inspect the original job checkpoint; Tasker should resume accounting without another send. |
| Usage action opens the wrong settings tab | Confirm the subscriptionManagement flag and use a current notification template. |
See Rotate secrets for API-key rotation and rollback.