feat(emails): verify-the-send building block — hasDeliveredEmail (DRAFT, needs design call)
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— countsstatus='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:
- 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.
- 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 inrunAgentWorkflowwhen the run was email-mandated andhasDeliveredEmail(chatId)is false; escalate to a run failure only once the signal proves clean. - Where to hook?
runAgentWorkflowpost-finish step vs. a post-run sweep overemail_send_log.
I've deliberately not wired a hard guarantee into runAgentWorkflow yet — that's the call to make on review.
Depends on
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. sendEmailHandlerrecords all outcomes (sent,send_failed,rejected) vialogEmailAttempt, capturing the raw body (viarequest.clone()), parse flag, truncatedraw_body(10k), lengths, andresend_id. Best-effort; never throws.countDeliveredEmails(chatId)andhasDeliveredEmail(chatId)to answer “did this run deliver at least one email?”- Unit tests for logging, handler integration, and delivery check.
- Reads/writes
-
Migration
- The
email_send_logtable is migrated in the recoupable/database repo (mono/database). Ensure that migration is deployed there before relying on this.
- The