Archived
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
93 lines
4.4 KiB
SQL
93 lines
4.4 KiB
SQL
-- 邮件收发(ADR-013):账号(配置 + 密封凭证 + 游标 + 跨进程租约)、入站留痕、逐订阅方投递留痕、出站留痕。
|
|
--
|
|
-- 腾讯企业邮箱两条接入路(IMAP/SMTP · 企业微信邮件 API)都没有带内容的推送,只能轮询拉:
|
|
-- 两个后端连同一个库时,一个信箱同一时刻只许一个进程在拉 —— `polling_until` 与
|
|
-- `sys.task.advancing_until` 同款(一条 SQL 判 + 占,带过期时间,进程崩了不锁死)。
|
|
-- 入站两把唯一键:重复拉到(时钟偏 / UIDVALIDITY 重锚 / wecom 十分钟重叠窗)一律静默跳过。
|
|
-- 入站 / 出站表对账号**不设外键**:账号删了留痕还在。
|
|
create table if not exists sys.mail_account (
|
|
id text primary key,
|
|
name text not null,
|
|
kind text not null check (kind in ('imap-smtp', 'wecom')),
|
|
address text not null default '',
|
|
display_name text not null default '',
|
|
enabled boolean not null default true,
|
|
is_default boolean not null default false,
|
|
poll_interval_sec integer not null default 30 check (poll_interval_sec between 10 and 3600),
|
|
channel_id text,
|
|
backfill_from timestamptz,
|
|
settings jsonb not null default '{}'::jsonb,
|
|
secret_cipher text,
|
|
cursor jsonb,
|
|
last_polled_at timestamptz,
|
|
last_poll_error text,
|
|
last_inbound_at timestamptz,
|
|
polling_until timestamptz,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
create unique index if not exists mail_account_name_key on sys.mail_account (name);
|
|
-- 默认发件账号至多一个 —— 用例先把别的置 false,这道索引兜底
|
|
create unique index if not exists mail_account_one_default on sys.mail_account ((true)) where is_default;
|
|
create index if not exists mail_account_due_idx on sys.mail_account (enabled, last_polled_at);
|
|
|
|
create table if not exists sys.mail_inbound (
|
|
id text primary key,
|
|
account_id text not null,
|
|
message_id text not null,
|
|
provider_ref text not null,
|
|
in_reply_to text,
|
|
reference_ids jsonb not null default '[]'::jsonb,
|
|
from_addr text not null default '',
|
|
from_name text not null default '',
|
|
to_list jsonb not null default '[]'::jsonb,
|
|
cc_list jsonb not null default '[]'::jsonb,
|
|
subject text not null default '',
|
|
text_body text,
|
|
html_body text,
|
|
sent_at timestamptz,
|
|
received_at timestamptz not null,
|
|
attachments jsonb not null default '[]'::jsonb,
|
|
raw bytea,
|
|
raw_note text not null default '',
|
|
size integer not null default 0,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
create unique index if not exists mail_inbound_message_key on sys.mail_inbound (account_id, message_id);
|
|
create unique index if not exists mail_inbound_provider_key on sys.mail_inbound (account_id, provider_ref);
|
|
create index if not exists mail_inbound_time_idx on sys.mail_inbound (received_at desc);
|
|
|
|
create table if not exists sys.mail_inbound_delivery (
|
|
mail_id text not null references sys.mail_inbound(id) on delete cascade,
|
|
subscriber text not null,
|
|
state text not null check (state in ('dispatching', 'done', 'failed', 'skipped')),
|
|
error text,
|
|
started_at timestamptz not null default now(),
|
|
finished_at timestamptz,
|
|
primary key (mail_id, subscriber)
|
|
);
|
|
|
|
create table if not exists sys.mail_outbound (
|
|
id text primary key,
|
|
account_id text not null,
|
|
message_id text not null,
|
|
idempotency_key text,
|
|
to_list jsonb not null,
|
|
cc_list jsonb not null default '[]'::jsonb,
|
|
bcc_list jsonb not null default '[]'::jsonb,
|
|
subject text not null,
|
|
text_body text,
|
|
html_body text,
|
|
in_reply_to text,
|
|
reference_ids jsonb not null default '[]'::jsonb,
|
|
attachments jsonb not null default '[]'::jsonb,
|
|
state text not null check (state in ('sending', 'sent', 'failed')),
|
|
error text,
|
|
provider_ref text,
|
|
requested_by jsonb not null default '{}'::jsonb,
|
|
created_at timestamptz not null default now(),
|
|
sent_at timestamptz
|
|
);
|
|
create unique index if not exists mail_outbound_idem_key on sys.mail_outbound (idempotency_key) where idempotency_key is not null;
|
|
create index if not exists mail_outbound_time_idx on sys.mail_outbound (created_at desc);
|