A provider 2xx means it accepted the message, not that anyone received it. Twilio answers 201 Created and the carrier may refuse seconds later, and nothing watching HTTP status codes will ever know. That is not hypothetical: the ledger shows 48 consecutive messages to one number, every one undelivered, going back to January - including a daily send for seven weeks. Each was recorded upstream as a success, and each was billed. Two decisions worth keeping. We poll rather than take a StatusCallback, because this relay binds to loopback on purpose and no carrier can reach it - polling costs one API call per run and keeps that property. And we reconcile against the provider's ledger rather than our own record, because our own record is exactly what was wrong, and it only knows about messages we sent; Twilio's knows about the ones another service sent too, which is how those 48 would have been caught. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
channel-exit
The estate's one way out to a person's inbox or phone.
Services that need to reach a human POST here instead of carrying a SendGrid key of their own. Credentials live in one place, one service can be turned off or rate-limited, and a bug in some worker cannot become a bill.
The contract
POST /send
Authorization: Bearer <CHANNEL_EXIT_TOKEN>
Content-Type: application/json
{"channel": "email|sms|whatsapp|push", "to": "...", "subject": "...", "body": "..."}
| Reply | When |
|---|---|
200 |
the provider accepted it — {"ok": true, "provider_status": 202, "id": "..."} |
400 |
unknown channel, or to is not an email / not E.164, or nothing to send |
401 |
missing or wrong bearer token |
501 |
whatsapp and push — no provider for them in this estate |
502 |
the provider refused or was unreachable; its status is in the body |
503 |
that channel's credentials are not configured here |
GET /healthz needs no token and reports which channels are wired.
The shape is fixed by its first consumer, reminders-service
(app/alerts.py: gateway_sender), which treats any 2xx as delivered.
Two rules
It publishes one host port, on 127.0.0.1, and nothing else. An
authenticated relay reachable from the internet is an open spam relay the
moment the token leaks — and that token is copied into every consumer's
environment. It gets no 0.0.0.0 binding, no bridge-IP binding, and no
cloudflared route, ever.
It does not log what it was asked to send. The log records channel, a redacted address, the provider's status and message id. Never a subject, never a body, never a credential.
Consumers
From the host: http://127.0.0.1:<CHANNEL_EXIT_PORT>/send
From another container: http://channel-exit:8080/send. The relay joins the
docker network its consumers already run on (CHANNEL_EXIT_NETWORK) and is
reached there by name, on the container port — CHANNEL_EXIT_PORT is a
host-side publish and does not exist inside that network.
A consumer's own 127.0.0.1 is that consumer, so a loopback gateway URL fails
every delivery while looking correctly configured. That is the one mistake to
watch for here.
Running it
cp .env.example .env && chmod 600 .env # fill in, then
docker compose up -d --build
Every value in .env is required except PROVIDER_TIMEOUT. CHANNEL_EXIT_TOKEN
has no default: the service refuses to start without one rather than coming up
as an unauthenticated relay.
Tests
pip install -r requirements-dev.txt && python -m pytest -q
The suite replaces urllib.request.urlopen with something that raises, so no
test can send a real message however it is written.