April 30, 2026
Scheduled report emails are now idempotent across overlapping cron invocations.
Scheduled report emails no longer duplicate when cron invocations overlap
Layer's scheduled report cron — the loop behind the recurring email digests configured under Plans and billing — previously did a SELECT of due schedules and then UPDATEd each schedule's next_run_at after sending. Two cron ticks landing in the same window (a Vercel cron retry, a manual trigger overlapping the regular run, or a multi-region replica firing twice) could both read the same schedule as still-due, both call sendEmail(), and both then advance next_run_at — leaving recipients with two copies of the same digest in their inbox for that window.
Idempotency now lives one level deeper than the schedule row. Before sending, the cron claims a (schedule_id, scheduled_for) tuple in a new internal scheduled_report_runs table via INSERT … ON CONFLICT DO NOTHING, where scheduled_for is pinned to the schedule's current next_run_at. The unique constraint on (schedule_id, scheduled_for) is the gate: a parallel tick that read the same due row attempts the same insert, hits the conflict, and is skipped without calling sendEmail(). The first claimer proceeds, sends, marks the run row sent with a completed_at, and advances next_run_at exactly once. Failed sends mark the run row failed with the error message, also stamp completed_at, and advance next_run_at (fail-forward — email delivery is not retried within the same window, so a transient outage doesn't pile up duplicate retries on the next tick).
The cron's response payload now carries an additional skipped counter alongside processed, sent, and failed so contention is visible in the existing observability surface. A back-to-back invocation against a single due schedule will now report processed: 1, sent: 1, skipped: 0 on the first call and processed: 1, sent: 0, skipped: 1 on the second, instead of two sends. The scheduled_report_runs table is service-role-only with default-deny RLS — it isn't exposed on any tenant API, and tenant-facing schedule configuration is unchanged.
No action is required on your part. Existing schedules continue to deliver on the same cadence; the only observable difference is that overlapping cron invocations no longer duplicate the email.
May 1, 2026
Layer API endpoints gain rate limiting and request-size guardrails, session enforcement is tightened, and Stripe webhook processing is now idempotent up front.
April 29, 2026
ANTAQ vessel-call cargo enrichment unblocked, Explorer score robustness badge, named portfolios with composite scoring, and Business Licenses dashboard.