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:
```ts
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/reschedule` succeeded. Details: `{ from, to, idempotency_token }`.
• `cancelled` — `DELETE /scheduled/:id` succeeded. 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-now` succeeded (rescheduled to "now + 1s").
## Action types — Broadcasts
• `created` — broadcast row created in draft state.
• `send_started` — `POST /broadcasts/:id/send` succeeded; status flipped `draft` → `sending`. Details: `{ total_recipients }`.
• `page_sent` — one fan-out page completed. Details: `{ page, sent, failed, skipped_unsubscribed }`.
• `cancel_requested` — `POST /broadcasts/:id/cancel` accepted. Details: `{ already_queued }`.
• `cancelled` — fan-out loop noticed status flipped to `cancelled` and stopped. Details: `{ pages_completed }`.
• `completed` — all recipients fanned out, status flipped to `sent`. 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:
```json
{
"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" } }
]
}
```