Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
68 lines
2.5 KiB
SQL
68 lines
2.5 KiB
SQL
-- +goose Up
|
|
-- Tenant email provider + unsubscribes + send log (Marketing suite).
|
|
-- Secrets: AES-GCM ciphertext (enc:v1:...) of JSON {api_key,smtp_*}; never log plaintext.
|
|
-- Prefer APP_ENCRYPTION_KEY for DeriveKey.
|
|
|
|
CREATE TABLE email_providers (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
provider_type TEXT NOT NULL DEFAULT 'smtp'
|
|
CHECK (provider_type IN ('smtp', 'resend', 'sendgrid')),
|
|
from_email TEXT NOT NULL DEFAULT '',
|
|
from_name TEXT NOT NULL DEFAULT '',
|
|
secrets_enc TEXT NOT NULL DEFAULT '',
|
|
config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
status TEXT NOT NULL DEFAULT 'unverified'
|
|
CHECK (status IN ('unverified', 'verified', 'error')),
|
|
verified_at TIMESTAMPTZ,
|
|
last_error TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (company_id)
|
|
);
|
|
|
|
CREATE INDEX email_providers_company_status_idx ON email_providers (company_id, status);
|
|
|
|
CREATE TABLE email_unsubscribes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
email TEXT NOT NULL,
|
|
email_hash TEXT NOT NULL,
|
|
token TEXT NOT NULL UNIQUE,
|
|
reason TEXT,
|
|
unsubscribed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (company_id, email_hash)
|
|
);
|
|
|
|
CREATE INDEX email_unsubscribes_company_idx ON email_unsubscribes (company_id);
|
|
|
|
CREATE TABLE email_sends (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
campaign_id UUID REFERENCES email_campaigns(id) ON DELETE SET NULL,
|
|
version_id UUID REFERENCES email_campaign_versions(id) ON DELETE SET NULL,
|
|
recipient_email TEXT NOT NULL,
|
|
recipient_hash TEXT NOT NULL,
|
|
kind TEXT NOT NULL DEFAULT 'blast'
|
|
CHECK (kind IN ('test', 'blast')),
|
|
status TEXT NOT NULL DEFAULT 'queued'
|
|
CHECK (status IN ('queued', 'sent', 'failed', 'skipped', 'unsubscribed')),
|
|
provider_message_id TEXT,
|
|
error TEXT,
|
|
sent_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX email_sends_company_created_idx ON email_sends (company_id, created_at DESC);
|
|
CREATE INDEX email_sends_campaign_idx ON email_sends (campaign_id);
|
|
|
|
CREATE UNIQUE INDEX email_sends_blast_idempotent_idx
|
|
ON email_sends (campaign_id, recipient_hash)
|
|
WHERE kind = 'blast' AND status IN ('queued', 'sent');
|
|
|
|
-- +goose Down
|
|
DROP TABLE IF EXISTS email_sends;
|
|
DROP TABLE IF EXISTS email_unsubscribes;
|
|
DROP TABLE IF EXISTS email_providers;
|