Files
channel-exit/scripts/reconcile.py
T
Nirav PatelandClaude Opus 5 349f03015f
CI / test (push) Successful in 5s
Ask the carrier what actually happened
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]>
2026-08-18 20:43:11 -04:00

68 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Check what the carrier did with the messages we handed over.
Run on a schedule. Exits non-zero when anything was accepted and then not
delivered, so a cron mailer or a supervisor surfaces it — the point being that
somebody hears about it. Forty-eight consecutive failures went unnoticed for
seven months because nothing ever asked this question.
python3 scripts/reconcile.py # last 24 hours
python3 scripts/reconcile.py --hours 168 # last week
python3 scripts/reconcile.py --email [email protected] # and post the report
Reporting by email goes through this relay's own send path, so the alert
travels the one channel that is known to work. If SMS is the thing that is
broken, an SMS alert about it would be the last thing to arrive.
"""
from __future__ import annotations
import argparse
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.config import Settings # noqa: E402
from app.reconcile import describe, fetch, reconcile, window_start # noqa: E402
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--hours", type=int, default=24, help="how far back to look")
parser.add_argument("--email", help="send the report to this address as well")
parser.add_argument("--quiet-when-clean", action="store_true",
help="print nothing when every message was delivered")
args = parser.parse_args()
settings = Settings()
if not (settings.twilio_sid and settings.twilio_token):
print("no Twilio credentials configured; nothing to reconcile", file=sys.stderr)
return 0
messages = fetch(account_sid=settings.twilio_sid, auth_token=settings.twilio_token,
since=window_start(args.hours), timeout=settings.timeout)
report = reconcile(messages)
text = describe(report)
if report.ok and args.quiet_when_clean:
return 0
print(text)
if args.email and report.failed:
# Only on failure: a daily "everything is fine" email is a thing people
# filter, and then the one that matters is filtered too.
from app.providers import send_email
outcome = send_email(
to=args.email, subject=f"SMS not delivered: {len(report.failed)} in the last {args.hours}h",
body=text, api_key=settings.sendgrid_key, sender=settings.email_from,
timeout=settings.timeout)
print(f"report emailed: provider status {outcome.status}", file=sys.stderr)
return 1 if report.failed else 0
if __name__ == "__main__":
raise SystemExit(main())