-- Legacy Mail Tables extracted from consolidated -- Global email signature (applied to all emails from this bot) CREATE TABLE IF NOT EXISTS global_email_signatures ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, name VARCHAR(100) NOT NULL DEFAULT 'Default', content_html TEXT NOT NULL, content_plain TEXT NOT NULL, position VARCHAR(20) DEFAULT 'bottom', is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_bot_global_signature UNIQUE (bot_id, name), CONSTRAINT check_signature_position CHECK (position IN ('top', 'bottom')) ); CREATE INDEX IF NOT EXISTS idx_global_signatures_bot ON global_email_signatures(bot_id) WHERE is_active = true; -- User email signatures (in addition to global) CREATE TABLE IF NOT EXISTS email_signatures ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, bot_id UUID REFERENCES bots(id) ON DELETE CASCADE, name VARCHAR(100) NOT NULL DEFAULT 'Default', content_html TEXT NOT NULL, content_plain TEXT NOT NULL, is_default BOOLEAN DEFAULT false, is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_user_signature_name UNIQUE (user_id, bot_id, name) ); CREATE INDEX IF NOT EXISTS idx_email_signatures_user ON email_signatures(user_id); CREATE INDEX IF NOT EXISTS idx_email_signatures_default ON email_signatures(user_id, bot_id) WHERE is_default = true; -- Scheduled emails (send later) CREATE TABLE IF NOT EXISTS scheduled_emails ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, to_addresses TEXT NOT NULL, cc_addresses TEXT, bcc_addresses TEXT, subject TEXT NOT NULL, body_html TEXT NOT NULL, body_plain TEXT, attachments_json TEXT DEFAULT '[]', scheduled_at TIMESTAMPTZ NOT NULL, sent_at TIMESTAMPTZ, status VARCHAR(20) DEFAULT 'pending', retry_count INTEGER DEFAULT 0, error_message TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT check_scheduled_status CHECK (status IN ('pending', 'sent', 'failed', 'cancelled')) ); CREATE INDEX IF NOT EXISTS idx_scheduled_emails_pending ON scheduled_emails(scheduled_at) WHERE status = 'pending'; CREATE INDEX IF NOT EXISTS idx_scheduled_emails_user ON scheduled_emails(user_id, bot_id); -- Email templates CREATE TABLE IF NOT EXISTS email_templates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, user_id UUID REFERENCES users(id) ON DELETE SET NULL, name VARCHAR(255) NOT NULL, description TEXT, subject_template TEXT NOT NULL, body_html_template TEXT NOT NULL, body_plain_template TEXT, variables_json TEXT DEFAULT '[]', category VARCHAR(100), is_shared BOOLEAN DEFAULT false, usage_count INTEGER DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_email_templates_bot ON email_templates(bot_id); CREATE INDEX IF NOT EXISTS idx_email_templates_category ON email_templates(category); CREATE INDEX IF NOT EXISTS idx_email_templates_shared ON email_templates(bot_id) WHERE is_shared = true; -- Auto-responders (Out of Office) - works with Stalwart Sieve CREATE TABLE IF NOT EXISTS email_auto_responders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, responder_type VARCHAR(50) NOT NULL DEFAULT 'out_of_office', subject TEXT NOT NULL, body_html TEXT NOT NULL, body_plain TEXT, start_date TIMESTAMPTZ, end_date TIMESTAMPTZ, send_to_internal_only BOOLEAN DEFAULT false, exclude_addresses TEXT, is_active BOOLEAN DEFAULT false, stalwart_sieve_id VARCHAR(255), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT check_responder_type CHECK (responder_type IN ('out_of_office', 'vacation', 'custom')), CONSTRAINT unique_user_responder UNIQUE (user_id, bot_id, responder_type) ); CREATE INDEX IF NOT EXISTS idx_auto_responders_active ON email_auto_responders(user_id, bot_id) WHERE is_active = true; -- Email rules/filters - synced with Stalwart Sieve CREATE TABLE IF NOT EXISTS email_rules ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, priority INTEGER DEFAULT 0, conditions_json TEXT NOT NULL, actions_json TEXT NOT NULL, stop_processing BOOLEAN DEFAULT false, is_active BOOLEAN DEFAULT true, stalwart_sieve_id VARCHAR(255), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_email_rules_user ON email_rules(user_id, bot_id); CREATE INDEX IF NOT EXISTS idx_email_rules_priority ON email_rules(user_id, bot_id, priority); -- Email labels/categories CREATE TABLE IF NOT EXISTS email_labels ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, name VARCHAR(100) NOT NULL, color VARCHAR(7) DEFAULT '#3b82f6', parent_id UUID REFERENCES email_labels(id) ON DELETE CASCADE, is_system BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_user_label UNIQUE (user_id, bot_id, name) ); CREATE INDEX IF NOT EXISTS idx_email_labels_user ON email_labels(user_id, bot_id); -- Email-label associations CREATE TABLE IF NOT EXISTS email_label_assignments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email_message_id VARCHAR(255) NOT NULL, label_id UUID NOT NULL REFERENCES email_labels(id) ON DELETE CASCADE, assigned_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_email_label UNIQUE (email_message_id, label_id) ); CREATE INDEX IF NOT EXISTS idx_label_assignments_email ON email_label_assignments(email_message_id); CREATE INDEX IF NOT EXISTS idx_label_assignments_label ON email_label_assignments(label_id); -- Distribution lists CREATE TABLE IF NOT EXISTS distribution_lists ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, email_alias VARCHAR(255), description TEXT, members_json TEXT NOT NULL DEFAULT '[]', is_public BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_distribution_lists_bot ON distribution_lists(bot_id); CREATE INDEX IF NOT EXISTS idx_distribution_lists_owner ON distribution_lists(owner_id); -- Shared mailboxes - managed via Stalwart CREATE TABLE IF NOT EXISTS shared_mailboxes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, email_address VARCHAR(255) NOT NULL, display_name VARCHAR(255) NOT NULL, description TEXT, settings_json TEXT DEFAULT '{}', stalwart_account_id VARCHAR(255), is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_shared_mailbox_email UNIQUE (bot_id, email_address) ); CREATE INDEX IF NOT EXISTS idx_shared_mailboxes_bot ON shared_mailboxes(bot_id); CREATE TABLE IF NOT EXISTS shared_mailbox_members ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), mailbox_id UUID NOT NULL REFERENCES shared_mailboxes(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, permission_level VARCHAR(20) DEFAULT 'read', added_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_mailbox_member UNIQUE (mailbox_id, user_id), CONSTRAINT check_permission CHECK (permission_level IN ('read', 'write', 'admin')) ); CREATE INDEX IF NOT EXISTS idx_shared_mailbox_members ON shared_mailbox_members(mailbox_id); CREATE INDEX IF NOT EXISTS idx_shared_mailbox_user ON shared_mailbox_members(user_id); -- Add user_email_accounts table for storing user email credentials CREATE TABLE IF NOT EXISTS user_email_accounts ( id uuid DEFAULT gen_random_uuid() NOT NULL, user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, email varchar(255) NOT NULL, display_name varchar(255) NULL, imap_server varchar(255) NOT NULL, imap_port int4 DEFAULT 993 NOT NULL, smtp_server varchar(255) NOT NULL, smtp_port int4 DEFAULT 587 NOT NULL, username varchar(255) NOT NULL, password_encrypted text NOT NULL, is_primary bool DEFAULT false NOT NULL, is_active bool DEFAULT true NOT NULL, created_at timestamptz DEFAULT now() NOT NULL, updated_at timestamptz DEFAULT now() NOT NULL, CONSTRAINT user_email_accounts_pkey PRIMARY KEY (id), CONSTRAINT user_email_accounts_user_email_key UNIQUE (user_id, email) ); CREATE INDEX IF NOT EXISTS idx_user_email_accounts_user_id ON user_email_accounts(user_id); CREATE INDEX IF NOT EXISTS idx_user_email_accounts_active ON user_email_accounts(is_active) WHERE is_active; -- Add email drafts table CREATE TABLE IF NOT EXISTS email_drafts ( id uuid DEFAULT gen_random_uuid() NOT NULL, user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, account_id uuid NOT NULL REFERENCES user_email_accounts(id) ON DELETE CASCADE, to_address text NOT NULL, cc_address text NULL, bcc_address text NULL, subject varchar(500) NULL, body text NULL, attachments jsonb DEFAULT '[]'::jsonb NOT NULL, created_at timestamptz DEFAULT now() NOT NULL, updated_at timestamptz DEFAULT now() NOT NULL, CONSTRAINT email_drafts_pkey PRIMARY KEY (id) ); CREATE INDEX IF NOT EXISTS idx_email_drafts_user_id ON email_drafts(user_id); CREATE INDEX IF NOT EXISTS idx_email_drafts_account_id ON email_drafts(account_id); -- Add email folders metadata table (for caching and custom folders) CREATE TABLE IF NOT EXISTS email_folders ( id uuid DEFAULT gen_random_uuid() NOT NULL, account_id uuid NOT NULL REFERENCES user_email_accounts(id) ON DELETE CASCADE, folder_name varchar(255) NOT NULL, folder_path varchar(500) NOT NULL, unread_count int4 DEFAULT 0 NOT NULL, total_count int4 DEFAULT 0 NOT NULL, last_synced timestamptz NULL, created_at timestamptz DEFAULT now() NOT NULL, updated_at timestamptz DEFAULT now() NOT NULL, CONSTRAINT email_folders_pkey PRIMARY KEY (id), CONSTRAINT email_folders_account_path_key UNIQUE (account_id, folder_path) ); CREATE INDEX IF NOT EXISTS idx_email_folders_account_id ON email_folders(account_id); -- Add sessions table enhancement for storing current email account -- Check if column exists, if not add it (idempotent) DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'user_sessions' AND column_name = 'active_email_account_id' ) THEN ALTER TABLE user_sessions ADD COLUMN active_email_account_id uuid NULL; ALTER TABLE user_sessions ADD CONSTRAINT user_sessions_email_account_id_fkey FOREIGN KEY (active_email_account_id) REFERENCES user_email_accounts(id) ON DELETE SET NULL; END IF; END $$; -- Email Read Tracking Table CREATE TABLE IF NOT EXISTS sent_email_tracking ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tracking_id UUID NOT NULL UNIQUE, bot_id UUID NOT NULL, account_id UUID NOT NULL, from_email VARCHAR(255) NOT NULL, to_email VARCHAR(255) NOT NULL, cc TEXT, bcc TEXT, subject TEXT NOT NULL, sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), is_read BOOLEAN NOT NULL DEFAULT FALSE, read_at TIMESTAMPTZ, read_count INTEGER NOT NULL DEFAULT 0, first_read_ip VARCHAR(45), last_read_ip VARCHAR(45), user_agent TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Indexes for efficient queries CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_tracking_id ON sent_email_tracking(tracking_id); CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_bot_id ON sent_email_tracking(bot_id); CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_account_id ON sent_email_tracking(account_id); CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_to_email ON sent_email_tracking(to_email); CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_sent_at ON sent_email_tracking(sent_at DESC); CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_is_read ON sent_email_tracking(is_read); CREATE INDEX IF NOT EXISTS idx_sent_email_tracking_read_status ON sent_email_tracking(bot_id, is_read, sent_at DESC); -- Trigger to auto-update updated_at for tracking CREATE OR REPLACE FUNCTION update_sent_email_tracking_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trigger_update_sent_email_tracking_updated_at ON sent_email_tracking; CREATE TRIGGER trigger_update_sent_email_tracking_updated_at BEFORE UPDATE ON sent_email_tracking FOR EACH ROW EXECUTE FUNCTION update_sent_email_tracking_updated_at(); -- Add comment for documentation COMMENT ON TABLE sent_email_tracking IS 'Tracks sent emails for read receipt functionality via tracking pixel'; -- Email monitoring table for ON EMAIL triggers CREATE TABLE IF NOT EXISTS email_monitors ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE, email_address VARCHAR(500) NOT NULL, script_path VARCHAR(1000) NOT NULL, is_active BOOLEAN DEFAULT true, last_check_at TIMESTAMPTZ, last_uid BIGINT DEFAULT 0, filter_from VARCHAR(500), filter_subject VARCHAR(500), created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, CONSTRAINT unique_bot_email UNIQUE (bot_id, email_address) ); CREATE INDEX IF NOT EXISTS idx_email_monitors_bot_id ON email_monitors(bot_id); CREATE INDEX IF NOT EXISTS idx_email_monitors_email ON email_monitors(email_address); CREATE INDEX IF NOT EXISTS idx_email_monitors_active ON email_monitors(is_active) WHERE is_active = true; -- Email received events log CREATE TABLE IF NOT EXISTS email_received_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), monitor_id UUID NOT NULL REFERENCES email_monitors(id) ON DELETE CASCADE, message_uid BIGINT NOT NULL, message_id VARCHAR(500), from_address VARCHAR(500) NOT NULL, to_addresses_json TEXT, subject VARCHAR(1000), received_at TIMESTAMPTZ, has_attachments BOOLEAN DEFAULT false, content_preview TEXT, processed BOOLEAN DEFAULT false, processed_at TIMESTAMPTZ, error_message TEXT, created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL ); CREATE INDEX IF NOT EXISTS idx_email_events_monitor ON email_received_events(monitor_id); CREATE INDEX IF NOT EXISTS idx_email_events_received ON email_received_events(received_at DESC); CREATE INDEX IF NOT EXISTS idx_email_events_processed ON email_received_events(processed) WHERE processed = false;