-- +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;