135 lines
5.8 KiB
SQL
135 lines
5.8 KiB
SQL
-- +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;
|