Say when a channel cannot be checked, instead of skipping it
CI / test (push) Successful in 5s

The estate's SendGrid key is send-only: mail.send and the batch scopes,
with no suppression.read, no bounces.read, no stats. So email delivery
cannot be reconciled the way SMS now is.

The tempting move is to check SMS and quietly say nothing about email.
That is precisely how a gap disappears - the daily report looks clean
and the unchecked channel stops being a question anyone asks. It now
reports 'email delivery NOT VERIFIED' with the exact scope that would
fix it, every day, until somebody does.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Nirav Patel
2026-08-18 20:50:37 -04:00
co-authored by Claude Opus 5
parent 349f03015f
commit 6b3a09ec1c
3 changed files with 118 additions and 2 deletions
+70
View File
@@ -169,3 +169,73 @@ def _get(url: str, *, headers: dict[str, str], timeout: float) -> Response:
def window_start(hours: int) -> datetime:
return datetime.now(timezone.utc) - timedelta(hours=hours)
# --------------------------------------------------------------- email
SUPPRESSION_URL = "https://api.sendgrid.com/v3/suppression/bounces"
# The scope a key needs before any of this can be answered. Named here so the
# daily report can say exactly what to change rather than "not available".
EMAIL_READ_SCOPE = "suppression.read"
@dataclass(frozen=True)
class EmailReport:
checked: bool
bounces: list[dict[str, Any]]
reason: str = ""
def summary(self) -> str:
if not self.checked:
return f"email delivery NOT VERIFIED — {self.reason}"
if not self.bounces:
return "email: no bounces in the window"
return f"email: {len(self.bounces)} bounced"
def email_bounces(
*,
api_key: str,
since: datetime,
timeout: float = 15.0,
get: Callable[..., Response] | None = None,
) -> EmailReport:
"""Bounces SendGrid recorded, if this key is allowed to ask.
The estate's key is send-only, so this normally reports that it could not
check. That is the point: an unanswerable question should appear in the
daily report as unanswered, not be quietly skipped. A silent skip is how
"we have monitoring" turns into seven months of unnoticed failures.
"""
if not api_key:
return EmailReport(checked=False, bounces=[], reason="no SendGrid key configured")
fetcher = get or _get
reply = fetcher(
f"{SUPPRESSION_URL}?start_time={int(since.timestamp())}",
headers={"Authorization": f"Bearer {api_key}"},
timeout=timeout,
)
if reply.status in (401, 403):
return EmailReport(
checked=False, bounces=[],
reason=(f"the SendGrid key cannot read bounces (needs {EMAIL_READ_SCOPE}); "
"it is send-only, so email failures are invisible here"))
if reply.status >= 400:
return EmailReport(checked=False, bounces=[], reason=f"SendGrid answered {reply.status}")
try:
rows = json.loads(reply.body or "[]")
except json.JSONDecodeError:
return EmailReport(checked=False, bounces=[], reason="SendGrid did not return JSON")
return EmailReport(checked=True, bounces=rows if isinstance(rows, list) else [])
def describe_email(report: EmailReport) -> str:
lines = [report.summary()]
for row in report.bounces[:10]:
address = str(row.get("email") or "")
masked = ("***@" + address.split("@")[-1]) if "@" in address else "***"
lines.append(f" {masked}: {str(row.get('reason') or '')[:90]}")
return "\n".join(lines)