feat(emails): verify-the-send building block — hasDeliveredEmail (DRAFT, needs design call)

Draft
SW
4mo ago
+290-0

Draft. Ships the verifiable building block + tests; the enforcement decision is open (below). Stacked on the email_send_log PR (#731) — merge that first.

What

hasDeliveredEmail(chatId) (and countDeliveredEmails) — reads email_send_log to answer "did this run actually deliver a non-empty email?" That's the check needed for the issue's verify-the-send item: a report task that finishes without delivering should be flagged, not reported as a silent success.

Why

The baseline showed ~30% of report-task runs made zero /api/emails attempts (e.g. Fat Beats weeklies: 13/15 runs sent nothing) yet the runs completed "successfully." Nothing checks delivery.

What's here (safe, tested)

  • lib/supabase/email_send_log/countDeliveredEmails.ts — counts status='sent' rows for a chat (best-effort; 0 on error).
  • lib/emails/hasDeliveredEmail.ts — count > 0.
  • Unit tests (delivered / not-delivered).

Open design decision (why this is a draft)

The building block is safe; where and how to enforce is not obvious and shouldn't be guessed:

  1. How do we know a task was supposed to email? Not every headless run must. Options: a flag on the scheduled action / a marker in the run input / infer from the prompt. Needs a decision.
  2. Warn vs. fail? A hard failure risks mis-failing legitimate non-email runs. Proposal: start with a structured warning/metric (no_email_delivered) emitted after a natural finish in runAgentWorkflow when the run was email-mandated and hasDeliveredEmail(chatId) is false; escalate to a run failure only once the signal proves clean.
  3. Where to hook? runAgentWorkflow post-finish step vs. a post-run sweep over email_send_log.

I've deliberately not wired a hard guarantee into runAgentWorkflow yet — that's the call to make on review.

Depends on

  • #731 (email_send_log table + types) — this PR reads that table. Merge #731 first.

Part of recoupable/chat#1829 (last item in the PR matrix). Base test.

🤖 Generated with Claude Code


Summary by cubic

Adds a durable email_send_log and logs every /api/emails attempt, plus hasDeliveredEmail(chatId) to verify that a run actually delivered an email. This supports the verify-the-send requirement in recoupable/chat#1829; enforcement wiring will follow.

  • New Features

    • Reads/writes public.email_send_log (types added) with a typed insert helper.
    • sendEmailHandler records all outcomes (sent, send_failed, rejected) via logEmailAttempt, capturing the raw body (via request.clone()), parse flag, truncated raw_body (10k), lengths, and resend_id. Best-effort; never throws.
    • countDeliveredEmails(chatId) and hasDeliveredEmail(chatId) to answer “did this run deliver at least one email?”
    • Unit tests for logging, handler integration, and delivery check.
  • Migration

    • The email_send_log table is migrated in the recoupable/database repo (mono/database). Ensure that migration is deployed there before relying on this.