-- +goose Up -- deliveries holds one durable record per accepted logical mail delivery. -- The (source, idempotency_key) UNIQUE constraint replaces the previous Redis -- idempotency keyspace: the durable row IS the idempotency reservation. -- next_attempt_at is populated for deliveries whose active attempt is due in -- the future and drives the attempt scheduler's `FOR UPDATE SKIP LOCKED` pull. CREATE TABLE deliveries ( delivery_id text PRIMARY KEY, resend_parent_delivery_id text NOT NULL DEFAULT '', source text NOT NULL, status text NOT NULL, payload_mode text NOT NULL, template_id text NOT NULL DEFAULT '', locale text NOT NULL DEFAULT '', locale_fallback_used boolean NOT NULL DEFAULT false, template_variables jsonb, attachments jsonb, subject text NOT NULL DEFAULT '', text_body text NOT NULL DEFAULT '', html_body text NOT NULL DEFAULT '', idempotency_key text NOT NULL, request_fingerprint text NOT NULL, idempotency_expires_at timestamptz NOT NULL, attempt_count integer NOT NULL DEFAULT 0, last_attempt_status text NOT NULL DEFAULT '', provider_summary text NOT NULL DEFAULT '', next_attempt_at timestamptz, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, sent_at timestamptz, suppressed_at timestamptz, failed_at timestamptz, dead_lettered_at timestamptz, CONSTRAINT deliveries_idempotency_unique UNIQUE (source, idempotency_key) ); -- Drives the scheduler's due-attempt pull. The partial predicate keeps the -- index narrow: rows in terminal status (sent/suppressed/failed/dead_letter) -- never appear here. CREATE INDEX deliveries_due_idx ON deliveries (next_attempt_at) WHERE next_attempt_at IS NOT NULL; -- Drives the recovery pass (deliveries currently held by an in-progress -- attempt whose worker may have crashed). CREATE INDEX deliveries_sending_idx ON deliveries (status) WHERE status = 'sending'; -- Newest-first listing index used by the operator delivery list surface. CREATE INDEX deliveries_listing_idx ON deliveries (created_at DESC, delivery_id DESC); -- Coarse status / source / template filters used by the operator listing. CREATE INDEX deliveries_status_idx ON deliveries (status); CREATE INDEX deliveries_source_idx ON deliveries (source); CREATE INDEX deliveries_template_id_idx ON deliveries (template_id) WHERE template_id <> ''; -- delivery_recipients normalises the SMTP envelope so future recipient- -- filtered listing slots in without touching the deliveries row layout. -- 'reply_to' addresses are stored for round-trip fidelity but excluded from -- the email index per the prior keyspace rule. CREATE TABLE delivery_recipients ( delivery_id text NOT NULL REFERENCES deliveries(delivery_id) ON DELETE CASCADE, kind text NOT NULL, position integer NOT NULL, email text NOT NULL, PRIMARY KEY (delivery_id, kind, position), CONSTRAINT delivery_recipients_kind_check CHECK (kind IN ('to', 'cc', 'bcc', 'reply_to')) ); CREATE INDEX delivery_recipients_email_idx ON delivery_recipients (email) WHERE kind <> 'reply_to'; -- attempts stores the immutable execution history of one delivery. attempt_no -- is monotonically increasing per delivery, starting at 1. CREATE TABLE attempts ( delivery_id text NOT NULL REFERENCES deliveries(delivery_id) ON DELETE CASCADE, attempt_no integer NOT NULL, status text NOT NULL, scheduled_for timestamptz NOT NULL, started_at timestamptz, finished_at timestamptz, provider_classification text NOT NULL DEFAULT '', provider_summary text NOT NULL DEFAULT '', PRIMARY KEY (delivery_id, attempt_no) ); -- dead_letters holds the operator-visible record for one delivery that -- exhausted automated handling. CREATE TABLE dead_letters ( delivery_id text PRIMARY KEY REFERENCES deliveries(delivery_id) ON DELETE CASCADE, final_attempt_no integer NOT NULL, failure_classification text NOT NULL, provider_summary text NOT NULL DEFAULT '', recovery_hint text NOT NULL DEFAULT '', created_at timestamptz NOT NULL ); -- delivery_payloads stores the raw generic-delivery attachment bundle -- referenced by the delivery row. The payload column carries the -- acceptgenericdelivery.DeliveryPayload JSON shape; raw attachment bytes -- remain inside that JSON value as base64 strings. CREATE TABLE delivery_payloads ( delivery_id text PRIMARY KEY REFERENCES deliveries(delivery_id) ON DELETE CASCADE, payload jsonb NOT NULL ); -- malformed_commands stores operator-visible records for stream commands the -- intake validator could not accept. CREATE TABLE malformed_commands ( stream_entry_id text PRIMARY KEY, delivery_id text NOT NULL DEFAULT '', source text NOT NULL DEFAULT '', idempotency_key text NOT NULL DEFAULT '', failure_code text NOT NULL, failure_message text NOT NULL, raw_fields jsonb NOT NULL, recorded_at timestamptz NOT NULL ); -- Newest-first listing index used by the operator malformed-command list. CREATE INDEX malformed_commands_listing_idx ON malformed_commands (recorded_at DESC, stream_entry_id DESC); -- +goose Down DROP TABLE IF EXISTS malformed_commands; DROP TABLE IF EXISTS delivery_payloads; DROP TABLE IF EXISTS dead_letters; DROP TABLE IF EXISTS attempts; DROP TABLE IF EXISTS delivery_recipients; DROP TABLE IF EXISTS deliveries;