-- v0.18.0 Slice 4: outbound_emails audit table. -- -- Per the v0.18.0 email + webhook hygiene proposal ยง3, every send -- helper writes a row to this table before returning, regardless -- of outcome. status='sent' on success, 'failed' on exception, -- 'deferred' on the dev-fallback path (no SMTP_HOST configured). -- -- The table is queried by `GET /api/admin/outbound-emails` to -- answer "did this person ever get their invite?" without having -- to grep VM logs, and by the v0.18.0 Slice 5 bounce-correlation -- hook (which looks up message_id when a POST lands at -- /api/webhooks/email-bounce and marks the matching row -- status='bounced'). CREATE TABLE IF NOT EXISTS outbound_emails ( id INTEGER PRIMARY KEY, to_address TEXT NOT NULL, from_address TEXT NOT NULL, subject TEXT NOT NULL, kind TEXT NOT NULL, -- 'otc' | 'invite' | 'notification' | 'bundle' | 'digest' | 'rfc-invite' sent_at TEXT NOT NULL, -- ISO 8601, time the send was attempted status TEXT NOT NULL, -- 'sent' | 'failed' | 'deferred' | 'bounced' error TEXT, -- exception class + message if status='failed' notification_id INTEGER, -- nullable FK to notifications.id for the watcher path message_id TEXT -- the Message-ID header value, for bounce correlation ); CREATE INDEX IF NOT EXISTS idx_outbound_emails_to ON outbound_emails(to_address); CREATE INDEX IF NOT EXISTS idx_outbound_emails_sent_at ON outbound_emails(sent_at); CREATE INDEX IF NOT EXISTS idx_outbound_emails_message ON outbound_emails(message_id);