## TL;DR
Duplicate morning briefings mean two scheduler triggers fired or the idempotency key was not checked before sending. Make the send step check a sent-log for today's briefing ID before dispatching, so a second trigger becomes a no-op instead of a second email.

## The error
```text
duplicate send detected: briefing 2026-10-11-morning sent at 06:00:03 and 06:00:41
scheduler log shows 2 trigger events 38s apart.
```

## Fix it
1. Check the scheduler logs for double triggers: overlapping cron entries, a retry after a slow first run, or two scheduler instances.
   Expected: Expected: the log shows the two trigger events and their cause.
2. Add an idempotency check in the send step: query the sent-log for the briefing ID (date plus edition) and skip sending if it exists.
   Expected: Expected: a manually triggered duplicate run logs 'already sent, skipping' and sends nothing.
3. Make the sent-log write atomic with the send (write the log entry first, then send, or use a transaction) so a crash between them cannot double-send.
   Expected: Expected: no window exists where a retry can send twice.
4. Alert on skipped duplicates so you see the double triggers in monitoring without spamming readers.
   Expected: Expected: the team sees trigger anomalies while subscribers get one email.

## When to use
- Your morning briefing went out twice to subscribers.
- You need the send path to be idempotent across scheduler retries.
- You run scheduled sends with at-least-once trigger semantics.

## When not to use
- The duplicates are different editions (morning vs midday) - that is intended, not a bug.
- The email provider duplicated the send - check provider logs before changing the scheduler.
- Subscribers report duplicates but logs show one send - that is a client-side rendering issue.

## Why it happens
Schedulers deliver at-least-once: retries, overlapping runs, and duplicate cron entries all produce two triggers for one edition. Without an idempotency check at the send step, each trigger sends independently. The 38-second gap is the classic signature of a retry firing after the first run was slow.

## Variant phrasings
### duplicate briefing email sent twice
Same failure and fix; some searchers phrase the query this way.
### briefing agent double send dedup
Same failure and fix; some searchers phrase the query this way.

## Edge cases
- Clock skew between scheduler instances can make both instances believe they are the primary.
- A send that times out after the email actually went out is the most common double-send trigger.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_tNzSP5CtD5e2WtLa5CT9rw
