feat: use postgres
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
-- +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;
|
||||
@@ -0,0 +1,19 @@
|
||||
// Package migrations exposes the embedded goose migration files used by Mail
|
||||
// Service to provision its `mail` schema in PostgreSQL.
|
||||
//
|
||||
// The embedded filesystem is consumed by `pkg/postgres.RunMigrations` during
|
||||
// mail-service startup and by `cmd/jetgen` when regenerating the
|
||||
// `internal/adapters/postgres/jet/` code against a transient PostgreSQL
|
||||
// instance.
|
||||
package migrations
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *.sql
|
||||
var fs embed.FS
|
||||
|
||||
// FS returns the embedded filesystem containing every numbered goose
|
||||
// migration shipped with Mail Service.
|
||||
func FS() embed.FS {
|
||||
return fs
|
||||
}
|
||||
Reference in New Issue
Block a user