Audit Log Schema
Append-only audit log JSONB on broadcasts and scheduled emails — every state change is recorded with a timestamp, action name, and per-action details.
Both broadcasts and scheduled_emails carry an audit_log JSONB column that records every state change. The log is append-only and best-effort — appends are async and never block the user-visible action, so a failure to write the audit entry doesn't roll back a successful cancel/send.
Schema
audit_log is an array of entries:
type AuditEntry = {
at: string; // ISO 8601 timestamp (UTC)
action: string; // see action types below
details?: object; // action-specific payload (optional)
};Action types — Scheduled emails
created— initial creation. Details:{ scheduled_for }.rescheduled—PATCH /scheduled/:id/reschedulesucceeded. Details:{ from, to, idempotency_token }.cancelled—DELETE /scheduled/:idsucceeded. Details:{ cancelled_by }.send_started— BullMQ job activated and SES dispatch began.sent— SES accepted the message. Details:{ message_id }.failed— SES rejected or processor error. Details:{ error }.send_now—POST /scheduled/:id/send-nowsucceeded (rescheduled to "now + 1s").
Action types — Broadcasts
created— broadcast row created in draft state.send_started—POST /broadcasts/:id/sendsucceeded; status flippeddraft→sending. Details:{ total_recipients }.page_sent— one fan-out page completed. Details:{ page, sent, failed, skipped_unsubscribed }.cancel_requested—POST /broadcasts/:id/cancelaccepted. Details:{ already_queued }.cancelled— fan-out loop noticed status flipped tocancelledand stopped. Details:{ pages_completed }.completed— all recipients fanned out, status flipped tosent. Details:{ total_recipients, total_failed }.failed— fan-out aborted before completion (e.g. domain unverified mid-send). Details:{ error }.
Reading the audit log
The audit_log array is included in GET /scheduled/:id and broadcast detail responses. The dashboard surfaces it as a timeline in the broadcast/scheduled details modal, so you can see every state change at a glance.
For programmatic use:
{
"id": "uuid",
"status": "cancelled",
"audit_log": [
{ "at": "2026-04-15T10:00:00Z", "action": "created" },
{ "at": "2026-04-15T10:05:00Z", "action": "cancelled", "details": { "cancelled_by": "user-uuid" } }
]
}