Archived
mail.send 工具契约 2.0.0(to 数组 / html / 附件 / inReplyTo / dryRun;保留 v1 的 body 与 queued);契约快照刷新,五道门禁绿
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* module: mail —— 对外类型(编码规范 §10.1;设计稿 §5.1)。
|
||||||
|
*/
|
||||||
|
import type { MailAccountInput, MailAccountView, MailDeliveryView, MailInboundDetail, MailInboundSummary, MailOutboundView, MailPollOutcome, MailProbeReport, MailSendInput, MailSendResult } from '@contracts';
|
||||||
|
export type { MailAccountInput, MailAccountView, MailDeliveryView, MailInboundDetail, MailInboundSummary, MailOutboundView, MailPollOutcome, MailProbeReport, MailSendInput, MailSendResult, };
|
||||||
|
export { MailAccountDisabledError, MailAccountNameTakenError, MailAccountNotFoundError, MailAttachmentNotFoundError, MailInboundNotFoundError, MailNoDefaultAccountError, MailPollBusyError, MailSecretMissingError, MailSendInvalidError, MailSubscriberTakenError, MailSubscriberUnknownError, } from './internal/domain/errors.ts';
|
||||||
|
/**
|
||||||
|
* 订阅方回调 —— 「注册回调读到邮件内容」的落点。
|
||||||
|
* 返回 `{skipped}` 表示这封信与我无关(留痕 skipped,不算失败);抛错留痕 failed(原话脱敏)。
|
||||||
|
* **自动只投一次,不自动重试**(设计稿 §9.3);人按「重新投递」才再来。
|
||||||
|
*/
|
||||||
|
export type InboundHandler = (mail: MailInboundDetail, ctx: {
|
||||||
|
readonly accountId: string;
|
||||||
|
readonly channelId: string | undefined;
|
||||||
|
}) => Promise<void | {
|
||||||
|
readonly skipped: string;
|
||||||
|
}>;
|
||||||
|
export type MailService = {
|
||||||
|
listAccounts(): Promise<MailAccountView[]>;
|
||||||
|
getAccount(accountId: string): Promise<MailAccountView>;
|
||||||
|
createAccount(input: MailAccountInput): Promise<MailAccountView>;
|
||||||
|
/** secret 缺省 = 不改密文;kind / settings 变了游标清空重锚 */
|
||||||
|
updateAccount(accountId: string, input: MailAccountInput): Promise<MailAccountView>;
|
||||||
|
/** 留痕不删(入站 / 出站表对账号不设外键) */
|
||||||
|
deleteAccount(accountId: string): Promise<void>;
|
||||||
|
/** 已存账号用库里凭证;未存用入参里的;一个字节都不落库(wecom 探到地址的已存账号除外:回填 address) */
|
||||||
|
probe(target: {
|
||||||
|
accountId: string;
|
||||||
|
} | {
|
||||||
|
input: MailAccountInput;
|
||||||
|
accountId?: string | undefined;
|
||||||
|
}): Promise<MailProbeReport>;
|
||||||
|
/** 工作者用:到期且未被占的账号各拉一轮(账号间并行) */
|
||||||
|
pollDue(now: Date): Promise<MailPollOutcome[]>;
|
||||||
|
/** 路由用:租约被占 → MailPollBusyError;停用 → MailAccountDisabledError */
|
||||||
|
pollNow(accountId: string): Promise<MailPollOutcome>;
|
||||||
|
/** 同名重复注册 → MailSubscriberTakenError;返回注销函数 */
|
||||||
|
subscribe(name: string, handler: InboundHandler): () => void;
|
||||||
|
listInbound(query: {
|
||||||
|
accountId?: string | undefined;
|
||||||
|
limit?: number | undefined;
|
||||||
|
}): Promise<MailInboundSummary[]>;
|
||||||
|
getInbound(mailId: string): Promise<MailInboundDetail>;
|
||||||
|
attachment(mailId: string, index: number): Promise<{
|
||||||
|
name: string;
|
||||||
|
contentType: string;
|
||||||
|
bytes: Uint8Array;
|
||||||
|
}>;
|
||||||
|
/** 原件 .eml;超限没存 → MailAttachmentNotFoundError(index = -1) */
|
||||||
|
raw(mailId: string): Promise<{
|
||||||
|
name: string;
|
||||||
|
bytes: Uint8Array;
|
||||||
|
}>;
|
||||||
|
/** 人按的钮:指定订阅方或全部;正在 dispatching 的那条跳过 */
|
||||||
|
redeliver(mailId: string, subscriber?: string): Promise<MailDeliveryView[]>;
|
||||||
|
/** 先落 sending 行再经 provider 发;厂商失败落 failed 并以 state='failed' 返回,不抛 */
|
||||||
|
send(input: MailSendInput): Promise<MailSendResult>;
|
||||||
|
listOutbound(query: {
|
||||||
|
accountId?: string | undefined;
|
||||||
|
limit?: number | undefined;
|
||||||
|
}): Promise<MailOutboundView[]>;
|
||||||
|
};
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* module: mail —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。
|
||||||
|
*/
|
||||||
|
export type { InboundHandler, MailAccountInput, MailAccountView, MailDeliveryView, MailInboundDetail, MailInboundSummary, MailOutboundView, MailPollOutcome, MailProbeReport, MailSendInput, MailSendResult, MailService, } from './contract.ts';
|
||||||
|
export { MailAccountDisabledError, MailAccountNameTakenError, MailAccountNotFoundError, MailAttachmentNotFoundError, MailInboundNotFoundError, MailNoDefaultAccountError, MailPollBusyError, MailSecretMissingError, MailSendInvalidError, MailSubscriberTakenError, MailSubscriberUnknownError, } from './contract.ts';
|
||||||
|
export type { AttachmentStore, ChangeNotifier, Clock, DeliveryRecord, IdGenerator, InboundRecord, InboundStore, MailAccountRecord, MailAccountStore, MailCursor, MailProvider, MimeParser, OutboundMessage, OutboundRecord, OutboundStore, ParsedMail, ProviderAccount, RawMessage, SecretBox, } from './port.ts';
|
||||||
|
export { advanceImap, cursorSummary, imapCursor, imapNeedsReanchor, newUids, parseCursor, wecomCursor, wecomWindow, } from './internal/domain/cursor.ts';
|
||||||
|
export { isDue, isPolling, leaseMsFor } from './internal/domain/due.ts';
|
||||||
|
export { ATTACHMENT_CAP, bodyTextOf, buildInboundRecord, detailOf, planAttachments, RAW_CAP, stripHtml, summaryOf, threadIdOf, } from './internal/domain/inbound-record.ts';
|
||||||
|
export { errorText, redactSecrets, secretVariants } from './internal/domain/redact.ts';
|
||||||
|
export { messageIdFor, pickAccount, validateSend } from './internal/domain/send-input.ts';
|
||||||
|
export { canRedeliver, stateOfOutcome } from './internal/domain/delivery.ts';
|
||||||
|
export { cursorReset, mergeAccount, secretOf, viewOf } from './internal/domain/account-view.ts';
|
||||||
|
export { createInboundStore, createMailAccountStore, createOutboundStore } from './internal/adapter/mail-store.ts';
|
||||||
|
export { mailAccountTable, mailInboundDeliveryTable, mailInboundTable, mailOutboundTable } from './internal/adapter/mail-schema.ts';
|
||||||
|
export { createPostalMimeParser } from './internal/adapter/postal-mime-parser.ts';
|
||||||
|
export type { MailServiceDeps } from './internal/usecase/mail-service.ts';
|
||||||
|
export { createMailService } from './internal/usecase/mail-service.ts';
|
||||||
|
export { createImapSmtpProvider } from './internal/adapter/imap-smtp-provider.ts';
|
||||||
|
export type { FetchLike } from './internal/adapter/wecom-provider.ts';
|
||||||
|
export { createWecomProvider, WecomApiError } from './internal/adapter/wecom-provider.ts';
|
||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* module: mail —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3;设计稿 §5.2)。
|
||||||
|
*/
|
||||||
|
import type { MailAccountKind, MailProbeReport } from '@contracts';
|
||||||
|
import type { MailCursor } from './internal/domain/cursor.ts';
|
||||||
|
import type { DeliveryRecord, InboundRecord, MailAccountRecord, OutboundMessage, OutboundRecord, ParsedMail } from './internal/domain/records.ts';
|
||||||
|
export type { DeliveryRecord, InboundRecord, MailAccountRecord, MailCursor, OutboundMessage, OutboundRecord, ParsedMail };
|
||||||
|
/** 凭证密封 —— 与 llm / toolbox 同款,app 注入同一实例(ADR-009) */
|
||||||
|
export type SecretBox = {
|
||||||
|
seal(plaintext: string): string;
|
||||||
|
open(cipher: string): string;
|
||||||
|
};
|
||||||
|
export type Clock = {
|
||||||
|
now(): Date;
|
||||||
|
};
|
||||||
|
export type IdGenerator = {
|
||||||
|
next(): string;
|
||||||
|
};
|
||||||
|
export type MailAccountStore = {
|
||||||
|
list(): Promise<MailAccountRecord[]>;
|
||||||
|
get(id: string): Promise<MailAccountRecord | undefined>;
|
||||||
|
findByName(name: string): Promise<MailAccountRecord | undefined>;
|
||||||
|
/** upsert;`isDefault:true` 时先把别的置 false(同一事务) */
|
||||||
|
save(record: MailAccountRecord): Promise<void>;
|
||||||
|
remove(id: string): Promise<void>;
|
||||||
|
/** enabled 且(从没拉过 或 last_polled_at + interval <= now)且租约空 / 过期 */
|
||||||
|
listDue(now: Date): Promise<MailAccountRecord[]>;
|
||||||
|
/** 一条 SQL 判 + 占(设计稿 §9.1);抢到 true */
|
||||||
|
claimPolling(id: string, leaseMs: number): Promise<boolean>;
|
||||||
|
releasePolling(id: string): Promise<void>;
|
||||||
|
/** 拉取过程的进度:游标 / 时刻 / 错误 / 最近来信 / 探到的地址;行不存在 = no-op */
|
||||||
|
savePoll(id: string, patch: {
|
||||||
|
cursor?: MailCursor;
|
||||||
|
polledAt?: Date;
|
||||||
|
error?: string | null;
|
||||||
|
lastInboundAt?: Date;
|
||||||
|
address?: string;
|
||||||
|
}): Promise<void>;
|
||||||
|
};
|
||||||
|
export type InboundStore = {
|
||||||
|
/** (account_id, message_id) / (account_id, provider_ref) 冲突 → inserted:false,不抛 */
|
||||||
|
insert(record: InboundRecord): Promise<{
|
||||||
|
inserted: boolean;
|
||||||
|
}>;
|
||||||
|
knownProviderRefs(accountId: string, refs: readonly string[]): Promise<Set<string>>;
|
||||||
|
list(query: {
|
||||||
|
accountId?: string | undefined;
|
||||||
|
limit: number;
|
||||||
|
}): Promise<Array<{
|
||||||
|
record: InboundRecord;
|
||||||
|
deliveries: DeliveryRecord[];
|
||||||
|
}>>;
|
||||||
|
get(mailId: string): Promise<{
|
||||||
|
record: InboundRecord;
|
||||||
|
deliveries: DeliveryRecord[];
|
||||||
|
} | undefined>;
|
||||||
|
/** upsert on (mail_id, subscriber) */
|
||||||
|
setDelivery(delivery: DeliveryRecord): Promise<void>;
|
||||||
|
};
|
||||||
|
export type OutboundStore = {
|
||||||
|
insert(record: OutboundRecord): Promise<void>;
|
||||||
|
byIdempotencyKey(key: string): Promise<OutboundRecord | undefined>;
|
||||||
|
setState(id: string, state: 'sent' | 'failed', patch: {
|
||||||
|
error?: string;
|
||||||
|
providerRef?: string;
|
||||||
|
sentAt?: Date;
|
||||||
|
}): Promise<void>;
|
||||||
|
list(query: {
|
||||||
|
accountId?: string | undefined;
|
||||||
|
limit: number;
|
||||||
|
}): Promise<OutboundRecord[]>;
|
||||||
|
};
|
||||||
|
/** 附件存放 —— app 注入 = `sys.upload`(复用 8MB 上限、文本抽取、下载路由) */
|
||||||
|
export type AttachmentStore = {
|
||||||
|
put(file: {
|
||||||
|
name: string;
|
||||||
|
contentType: string;
|
||||||
|
bytes: Uint8Array;
|
||||||
|
note: string;
|
||||||
|
}): Promise<{
|
||||||
|
ref: string;
|
||||||
|
}>;
|
||||||
|
get(ref: string): Promise<{
|
||||||
|
name: string;
|
||||||
|
contentType: string;
|
||||||
|
bytes: Uint8Array;
|
||||||
|
} | undefined>;
|
||||||
|
};
|
||||||
|
/** 交给 provider 的账号:凭证明文只在这一层与 usecase 之间传,不落任何留痕 */
|
||||||
|
export type ProviderAccount = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly kind: MailAccountKind;
|
||||||
|
readonly address: string;
|
||||||
|
readonly displayName: string;
|
||||||
|
readonly settings: Record<string, unknown>;
|
||||||
|
readonly secret: Record<string, string>;
|
||||||
|
};
|
||||||
|
export type RawMessage = {
|
||||||
|
readonly providerRef: string;
|
||||||
|
readonly eml: Uint8Array;
|
||||||
|
readonly receivedAt: Date;
|
||||||
|
/** 这一封之后游标该在哪(IMAP 逐封给;wecom 不给,整批完才推进) */
|
||||||
|
readonly cursorAfter?: MailCursor | undefined;
|
||||||
|
};
|
||||||
|
/** 外界:一种账号类型一个适配器(设计稿 §5.5) */
|
||||||
|
export type MailProvider = {
|
||||||
|
readonly kind: MailAccountKind;
|
||||||
|
probe(account: ProviderAccount): Promise<MailProbeReport>;
|
||||||
|
/** 无游标时锚定初始游标(不回填;设了 backfillFrom 才从那天起) */
|
||||||
|
anchor(account: ProviderAccount, backfillFrom: Date | null, now: Date): Promise<MailCursor>;
|
||||||
|
pull(account: ProviderAccount, cursor: MailCursor, opts: {
|
||||||
|
readonly limit: number;
|
||||||
|
readonly now: Date;
|
||||||
|
readonly backfillFrom: Date | null;
|
||||||
|
/** 库里已有的 provider_ref —— wecom 用它在 read_mail 之前把重叠窗里的旧信滤掉;IMAP 靠 UID 游标不用 */
|
||||||
|
readonly known: (refs: readonly string[]) => Promise<Set<string>>;
|
||||||
|
}): Promise<{
|
||||||
|
messages: RawMessage[];
|
||||||
|
cursor: MailCursor;
|
||||||
|
more: boolean;
|
||||||
|
}>;
|
||||||
|
send(account: ProviderAccount, message: OutboundMessage): Promise<{
|
||||||
|
providerRef?: string | undefined;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
export type MimeParser = {
|
||||||
|
parse(eml: Uint8Array): Promise<ParsedMail>;
|
||||||
|
};
|
||||||
|
/** 变更通知 —— app 接到 broadcaster;模块不知道 SSE 的存在 */
|
||||||
|
export type ChangeNotifier = {
|
||||||
|
changed(reason: string): void;
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -2,3 +2,4 @@ export * from './http.ts';
|
|||||||
export * from './workbench.ts';
|
export * from './workbench.ts';
|
||||||
export * from './governance.ts';
|
export * from './governance.ts';
|
||||||
export * from './ingest.ts';
|
export * from './ingest.ts';
|
||||||
|
export * from './mail.ts';
|
||||||
|
|||||||
+690
@@ -0,0 +1,690 @@
|
|||||||
|
/**
|
||||||
|
* 邮件收发的路由契约(设计稿 §7.2;ADR-013)。
|
||||||
|
*
|
||||||
|
* 读写都归运营中心(设计稿 §7.2 + 2026-08-26 裁决):邮箱那一屏落在运营中心,设计端没有消费方;
|
||||||
|
* 而入站正文装的是客户来往内容 —— ADR-012 已把「设计端能读客户主数据」记为一处后果,不再无故加宽。
|
||||||
|
* `/api/gov/*` 的写路由由 app 中间件自动广播 `{kind:'system', id:'gov'}`。
|
||||||
|
* 探测 / 试发 / 拉取的**厂商失败不是接口错误**:200 的 body 里带原话(脱敏)。
|
||||||
|
*/
|
||||||
|
import { z } from 'zod';
|
||||||
|
import type { RouteContract } from './http.ts';
|
||||||
|
export declare const listMailAccountsRoute: {
|
||||||
|
readonly method: "GET";
|
||||||
|
readonly path: "/api/gov/mail/accounts";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
accounts: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"imap-smtp">;
|
||||||
|
}, z.core.$strip>, z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"wecom">;
|
||||||
|
}, z.core.$strip>], "kind">>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
export declare const createMailAccountRoute: {
|
||||||
|
readonly method: "POST";
|
||||||
|
readonly path: "/api/gov/mail/accounts";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly body: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
||||||
|
settings: z.ZodPrefault<z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
secret: z.ZodOptional<z.ZodObject<{
|
||||||
|
password: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodDefault<z.ZodString>;
|
||||||
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
isDefault: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
pollIntervalSec: z.ZodDefault<z.ZodNumber>;
|
||||||
|
channelId: z.ZodDefault<z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ChannelId", "out">>>;
|
||||||
|
backfillFrom: z.ZodDefault<z.ZodNullable<z.ZodISODateTime>>;
|
||||||
|
kind: z.ZodLiteral<"imap-smtp">;
|
||||||
|
address: z.ZodEmail;
|
||||||
|
}, z.core.$strip>, z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
secret: z.ZodOptional<z.ZodObject<{
|
||||||
|
corpSecret: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodDefault<z.ZodString>;
|
||||||
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
isDefault: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
pollIntervalSec: z.ZodDefault<z.ZodNumber>;
|
||||||
|
channelId: z.ZodDefault<z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ChannelId", "out">>>;
|
||||||
|
backfillFrom: z.ZodDefault<z.ZodNullable<z.ZodISODateTime>>;
|
||||||
|
kind: z.ZodLiteral<"wecom">;
|
||||||
|
}, z.core.$strip>], "kind">;
|
||||||
|
readonly output: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"imap-smtp">;
|
||||||
|
}, z.core.$strip>, z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"wecom">;
|
||||||
|
}, z.core.$strip>], "kind">;
|
||||||
|
};
|
||||||
|
/** 未存的配置探测 —— 表单上正填的那份,一个字节都不落库 */
|
||||||
|
export declare const probeMailDraftRoute: {
|
||||||
|
readonly method: "POST";
|
||||||
|
readonly path: "/api/gov/mail/accounts/probe";
|
||||||
|
readonly access: "ops";
|
||||||
|
/** 改已存账号时带上 ?accountId=:表单没重填密码就借库里那份密文 */
|
||||||
|
readonly query: z.ZodObject<{
|
||||||
|
accountId: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly body: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
||||||
|
settings: z.ZodPrefault<z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
secret: z.ZodOptional<z.ZodObject<{
|
||||||
|
password: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodDefault<z.ZodString>;
|
||||||
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
isDefault: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
pollIntervalSec: z.ZodDefault<z.ZodNumber>;
|
||||||
|
channelId: z.ZodDefault<z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ChannelId", "out">>>;
|
||||||
|
backfillFrom: z.ZodDefault<z.ZodNullable<z.ZodISODateTime>>;
|
||||||
|
kind: z.ZodLiteral<"imap-smtp">;
|
||||||
|
address: z.ZodEmail;
|
||||||
|
}, z.core.$strip>, z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
secret: z.ZodOptional<z.ZodObject<{
|
||||||
|
corpSecret: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodDefault<z.ZodString>;
|
||||||
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
isDefault: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
pollIntervalSec: z.ZodDefault<z.ZodNumber>;
|
||||||
|
channelId: z.ZodDefault<z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ChannelId", "out">>>;
|
||||||
|
backfillFrom: z.ZodDefault<z.ZodNullable<z.ZodISODateTime>>;
|
||||||
|
kind: z.ZodLiteral<"wecom">;
|
||||||
|
}, z.core.$strip>], "kind">;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
kind: z.ZodEnum<{
|
||||||
|
"imap-smtp": "imap-smtp";
|
||||||
|
wecom: "wecom";
|
||||||
|
}>;
|
||||||
|
address: z.ZodOptional<z.ZodString>;
|
||||||
|
receive: z.ZodObject<{
|
||||||
|
ok: z.ZodBoolean;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
latencyMs: z.ZodOptional<z.ZodNumber>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
send: z.ZodObject<{
|
||||||
|
checked: z.ZodBoolean;
|
||||||
|
ok: z.ZodOptional<z.ZodBoolean>;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
note: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
export declare const getMailAccountRoute: {
|
||||||
|
readonly method: "GET";
|
||||||
|
readonly path: "/api/gov/mail/accounts/:accountId";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
accountId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"imap-smtp">;
|
||||||
|
}, z.core.$strip>, z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"wecom">;
|
||||||
|
}, z.core.$strip>], "kind">;
|
||||||
|
};
|
||||||
|
/** secret 缺省 = 不改密文;kind / settings 变了游标清空重锚 */
|
||||||
|
export declare const updateMailAccountRoute: {
|
||||||
|
readonly method: "PUT";
|
||||||
|
readonly path: "/api/gov/mail/accounts/:accountId";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
accountId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly body: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
||||||
|
settings: z.ZodPrefault<z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
secret: z.ZodOptional<z.ZodObject<{
|
||||||
|
password: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodDefault<z.ZodString>;
|
||||||
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
isDefault: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
pollIntervalSec: z.ZodDefault<z.ZodNumber>;
|
||||||
|
channelId: z.ZodDefault<z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ChannelId", "out">>>;
|
||||||
|
backfillFrom: z.ZodDefault<z.ZodNullable<z.ZodISODateTime>>;
|
||||||
|
kind: z.ZodLiteral<"imap-smtp">;
|
||||||
|
address: z.ZodEmail;
|
||||||
|
}, z.core.$strip>, z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
secret: z.ZodOptional<z.ZodObject<{
|
||||||
|
corpSecret: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodDefault<z.ZodString>;
|
||||||
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
isDefault: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
pollIntervalSec: z.ZodDefault<z.ZodNumber>;
|
||||||
|
channelId: z.ZodDefault<z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ChannelId", "out">>>;
|
||||||
|
backfillFrom: z.ZodDefault<z.ZodNullable<z.ZodISODateTime>>;
|
||||||
|
kind: z.ZodLiteral<"wecom">;
|
||||||
|
}, z.core.$strip>], "kind">;
|
||||||
|
readonly output: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"imap-smtp">;
|
||||||
|
}, z.core.$strip>, z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"wecom">;
|
||||||
|
}, z.core.$strip>], "kind">;
|
||||||
|
};
|
||||||
|
export declare const deleteMailAccountRoute: {
|
||||||
|
readonly method: "DELETE";
|
||||||
|
readonly path: "/api/gov/mail/accounts/:accountId";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
accountId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
ok: z.ZodLiteral<true>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
/** 已存账号探测(用库里凭证);凭证空 412 */
|
||||||
|
export declare const probeMailAccountRoute: {
|
||||||
|
readonly method: "POST";
|
||||||
|
readonly path: "/api/gov/mail/accounts/:accountId/probe";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
accountId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
kind: z.ZodEnum<{
|
||||||
|
"imap-smtp": "imap-smtp";
|
||||||
|
wecom: "wecom";
|
||||||
|
}>;
|
||||||
|
address: z.ZodOptional<z.ZodString>;
|
||||||
|
receive: z.ZodObject<{
|
||||||
|
ok: z.ZodBoolean;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
latencyMs: z.ZodOptional<z.ZodNumber>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
send: z.ZodObject<{
|
||||||
|
checked: z.ZodBoolean;
|
||||||
|
ok: z.ZodOptional<z.ZodBoolean>;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
note: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
/** 立即拉一轮;另一进程占着租约 409;停用 412 */
|
||||||
|
export declare const pollMailAccountRoute: {
|
||||||
|
readonly method: "POST";
|
||||||
|
readonly path: "/api/gov/mail/accounts/:accountId/poll";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
accountId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
accountId: z.ZodString;
|
||||||
|
pulled: z.ZodNumber;
|
||||||
|
stored: z.ZodNumber;
|
||||||
|
dispatched: z.ZodNumber;
|
||||||
|
more: z.ZodBoolean;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
export declare const sendTestMailRoute: {
|
||||||
|
readonly method: "POST";
|
||||||
|
readonly path: "/api/gov/mail/accounts/:accountId/send-test";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
accountId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly body: z.ZodObject<{
|
||||||
|
to: z.ZodEmail;
|
||||||
|
subject: z.ZodString;
|
||||||
|
text: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
outboundId: z.ZodString;
|
||||||
|
accountId: z.ZodString;
|
||||||
|
messageId: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
sent: "sent";
|
||||||
|
}>;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
providerRef: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
export declare const listMailInboundRoute: {
|
||||||
|
readonly method: "GET";
|
||||||
|
readonly path: "/api/gov/mail/inbound";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly query: z.ZodObject<{
|
||||||
|
accountId: z.ZodOptional<z.ZodString>;
|
||||||
|
limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
mails: z.ZodArray<z.ZodObject<{
|
||||||
|
id: z.ZodString;
|
||||||
|
accountId: z.ZodString;
|
||||||
|
messageId: z.ZodString;
|
||||||
|
from: z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
to: z.ZodArray<z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
cc: z.ZodArray<z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
subject: z.ZodString;
|
||||||
|
sentAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
receivedAt: z.ZodISODateTime;
|
||||||
|
textPreview: z.ZodString;
|
||||||
|
hasHtml: z.ZodBoolean;
|
||||||
|
attachments: z.ZodArray<z.ZodObject<{
|
||||||
|
index: z.ZodNumber;
|
||||||
|
name: z.ZodString;
|
||||||
|
contentType: z.ZodString;
|
||||||
|
size: z.ZodNumber;
|
||||||
|
ref: z.ZodNullable<z.ZodString>;
|
||||||
|
note: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
deliveries: z.ZodArray<z.ZodObject<{
|
||||||
|
subscriber: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
done: "done";
|
||||||
|
dispatching: "dispatching";
|
||||||
|
skipped: "skipped";
|
||||||
|
}>;
|
||||||
|
error: z.ZodNullable<z.ZodString>;
|
||||||
|
startedAt: z.ZodISODateTime;
|
||||||
|
finishedAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
export declare const getMailInboundRoute: {
|
||||||
|
readonly method: "GET";
|
||||||
|
readonly path: "/api/gov/mail/inbound/:mailId";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
mailId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
id: z.ZodString;
|
||||||
|
accountId: z.ZodString;
|
||||||
|
messageId: z.ZodString;
|
||||||
|
from: z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
to: z.ZodArray<z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
cc: z.ZodArray<z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
subject: z.ZodString;
|
||||||
|
sentAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
receivedAt: z.ZodISODateTime;
|
||||||
|
textPreview: z.ZodString;
|
||||||
|
hasHtml: z.ZodBoolean;
|
||||||
|
attachments: z.ZodArray<z.ZodObject<{
|
||||||
|
index: z.ZodNumber;
|
||||||
|
name: z.ZodString;
|
||||||
|
contentType: z.ZodString;
|
||||||
|
size: z.ZodNumber;
|
||||||
|
ref: z.ZodNullable<z.ZodString>;
|
||||||
|
note: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
deliveries: z.ZodArray<z.ZodObject<{
|
||||||
|
subscriber: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
done: "done";
|
||||||
|
dispatching: "dispatching";
|
||||||
|
skipped: "skipped";
|
||||||
|
}>;
|
||||||
|
error: z.ZodNullable<z.ZodString>;
|
||||||
|
startedAt: z.ZodISODateTime;
|
||||||
|
finishedAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
inReplyTo: z.ZodNullable<z.ZodString>;
|
||||||
|
references: z.ZodArray<z.ZodString>;
|
||||||
|
text: z.ZodNullable<z.ZodString>;
|
||||||
|
html: z.ZodNullable<z.ZodString>;
|
||||||
|
size: z.ZodNumber;
|
||||||
|
rawAvailable: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
/** 附件原件(JSON + base64,与 /api/gov/upload/:id 同形);可带 ?ticket= 下载(ADR-012 §6) */
|
||||||
|
export declare const mailAttachmentRoute: {
|
||||||
|
readonly method: "GET";
|
||||||
|
readonly path: "/api/gov/mail/inbound/:mailId/attachments/:index";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
mailId: z.ZodString;
|
||||||
|
index: z.ZodCoercedNumber<unknown>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
name: z.ZodString;
|
||||||
|
contentType: z.ZodString;
|
||||||
|
dataBase64: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
/** 原件 .eml;超限没存 404 */
|
||||||
|
export declare const mailRawRoute: {
|
||||||
|
readonly method: "GET";
|
||||||
|
readonly path: "/api/gov/mail/inbound/:mailId/raw";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
mailId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
name: z.ZodString;
|
||||||
|
contentType: z.ZodLiteral<"message/rfc822">;
|
||||||
|
dataBase64: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
/** 人按的钮:重新投给订阅方(缺省全部);从此是至少一次 */
|
||||||
|
export declare const redeliverMailInboundRoute: {
|
||||||
|
readonly method: "POST";
|
||||||
|
readonly path: "/api/gov/mail/inbound/:mailId/redeliver";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly params: z.ZodObject<{
|
||||||
|
mailId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly body: z.ZodDefault<z.ZodObject<{
|
||||||
|
subscriber: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
deliveries: z.ZodArray<z.ZodObject<{
|
||||||
|
subscriber: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
done: "done";
|
||||||
|
dispatching: "dispatching";
|
||||||
|
skipped: "skipped";
|
||||||
|
}>;
|
||||||
|
error: z.ZodNullable<z.ZodString>;
|
||||||
|
startedAt: z.ZodISODateTime;
|
||||||
|
finishedAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
export declare const listMailOutboundRoute: {
|
||||||
|
readonly method: "GET";
|
||||||
|
readonly path: "/api/gov/mail/outbound";
|
||||||
|
readonly access: "ops";
|
||||||
|
readonly query: z.ZodObject<{
|
||||||
|
accountId: z.ZodOptional<z.ZodString>;
|
||||||
|
limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
mails: z.ZodArray<z.ZodObject<{
|
||||||
|
id: z.ZodString;
|
||||||
|
accountId: z.ZodString;
|
||||||
|
messageId: z.ZodString;
|
||||||
|
to: z.ZodArray<z.ZodString>;
|
||||||
|
cc: z.ZodArray<z.ZodString>;
|
||||||
|
bcc: z.ZodArray<z.ZodString>;
|
||||||
|
subject: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
sending: "sending";
|
||||||
|
sent: "sent";
|
||||||
|
}>;
|
||||||
|
error: z.ZodNullable<z.ZodString>;
|
||||||
|
providerRef: z.ZodNullable<z.ZodString>;
|
||||||
|
requestedBy: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
sentAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
/** 全部 15 条 —— 文档总表与覆盖测试按它数 */
|
||||||
|
export declare const MAIL_ROUTES: readonly RouteContract[];
|
||||||
@@ -13,4 +13,5 @@ export * from './ledger.ts';
|
|||||||
export * from './process-package.ts';
|
export * from './process-package.ts';
|
||||||
export * from './manifest.ts';
|
export * from './manifest.ts';
|
||||||
export * from './org.ts';
|
export * from './org.ts';
|
||||||
|
export * from './mail.ts';
|
||||||
export * from './api/index.ts';
|
export * from './api/index.ts';
|
||||||
|
|||||||
+364
@@ -0,0 +1,364 @@
|
|||||||
|
/**
|
||||||
|
* 邮件收发的契约(设计稿 docs/superpowers/specs/2026-08-25-mail-design.md §7.1;ADR-013)。
|
||||||
|
*
|
||||||
|
* 只有 zod schema 与推导类型。账号视图**永不含密文**(ADR-009 §4):只回 `secretBound`。
|
||||||
|
* 入参按 `kind` 分两形 —— 标准协议(IMAP/SMTP)与企业微信邮件 API(应用邮箱),
|
||||||
|
* 两条路的共同点是「轮询拉 + MIME 解析」,腾讯没有带内容的推送。
|
||||||
|
*/
|
||||||
|
import { z } from 'zod';
|
||||||
|
export declare const MAIL_ACCOUNT_KINDS: readonly ["imap-smtp", "wecom"];
|
||||||
|
export declare const mailAccountKindSchema: z.ZodEnum<{
|
||||||
|
"imap-smtp": "imap-smtp";
|
||||||
|
wecom: "wecom";
|
||||||
|
}>;
|
||||||
|
export type MailAccountKind = z.infer<typeof mailAccountKindSchema>;
|
||||||
|
export declare const MAIL_ACCOUNT_KIND_LABEL: Readonly<Record<MailAccountKind, string>>;
|
||||||
|
/** 标准协议:默认值就是腾讯企业邮箱官方帮助给的服务器与 SSL 端口 */
|
||||||
|
export declare const imapSmtpSettingsSchema: z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type ImapSmtpSettings = z.infer<typeof imapSmtpSettingsSchema>;
|
||||||
|
/** 企业微信邮件 API:corpid + 自建应用 secret(密封,不在 settings 里) */
|
||||||
|
export declare const wecomSettingsSchema: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type WecomSettings = z.infer<typeof wecomSettingsSchema>;
|
||||||
|
export declare const mailAccountInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
||||||
|
settings: z.ZodPrefault<z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
secret: z.ZodOptional<z.ZodObject<{
|
||||||
|
password: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodDefault<z.ZodString>;
|
||||||
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
isDefault: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
pollIntervalSec: z.ZodDefault<z.ZodNumber>;
|
||||||
|
channelId: z.ZodDefault<z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ChannelId", "out">>>;
|
||||||
|
backfillFrom: z.ZodDefault<z.ZodNullable<z.ZodISODateTime>>;
|
||||||
|
kind: z.ZodLiteral<"imap-smtp">;
|
||||||
|
address: z.ZodEmail;
|
||||||
|
}, z.core.$strip>, z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
secret: z.ZodOptional<z.ZodObject<{
|
||||||
|
corpSecret: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodDefault<z.ZodString>;
|
||||||
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
isDefault: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
pollIntervalSec: z.ZodDefault<z.ZodNumber>;
|
||||||
|
channelId: z.ZodDefault<z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ChannelId", "out">>>;
|
||||||
|
backfillFrom: z.ZodDefault<z.ZodNullable<z.ZodISODateTime>>;
|
||||||
|
kind: z.ZodLiteral<"wecom">;
|
||||||
|
}, z.core.$strip>], "kind">;
|
||||||
|
export type MailAccountInput = z.infer<typeof mailAccountInputSchema>;
|
||||||
|
export declare const mailAccountStatusSchema: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export declare const mailAccountViewSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
imapHost: z.ZodDefault<z.ZodString>;
|
||||||
|
imapPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
imapTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
smtpHost: z.ZodDefault<z.ZodString>;
|
||||||
|
smtpPort: z.ZodDefault<z.ZodNumber>;
|
||||||
|
smtpTls: z.ZodDefault<z.ZodBoolean>;
|
||||||
|
login: z.ZodOptional<z.ZodString>;
|
||||||
|
folder: z.ZodDefault<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"imap-smtp">;
|
||||||
|
}, z.core.$strip>, z.ZodObject<{
|
||||||
|
settings: z.ZodObject<{
|
||||||
|
corpId: z.ZodString;
|
||||||
|
apiBase: z.ZodDefault<z.ZodURL>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
id: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
address: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
|
enabled: z.ZodBoolean;
|
||||||
|
isDefault: z.ZodBoolean;
|
||||||
|
pollIntervalSec: z.ZodNumber;
|
||||||
|
channelId: z.ZodNullable<z.ZodString>;
|
||||||
|
backfillFrom: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
secretBound: z.ZodBoolean;
|
||||||
|
status: z.ZodObject<{
|
||||||
|
lastPolledAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
lastPollError: z.ZodNullable<z.ZodString>;
|
||||||
|
lastInboundAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
cursor: z.ZodNullable<z.ZodString>;
|
||||||
|
polling: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
updatedAt: z.ZodISODateTime;
|
||||||
|
kind: z.ZodLiteral<"wecom">;
|
||||||
|
}, z.core.$strip>], "kind">;
|
||||||
|
export type MailAccountView = z.infer<typeof mailAccountViewSchema>;
|
||||||
|
/** 探测报告:收 / 发各说各的;wecom 的发信一栏 `checked:false` —— 拿到 token 不等于能发 */
|
||||||
|
export declare const mailProbeReportSchema: z.ZodObject<{
|
||||||
|
kind: z.ZodEnum<{
|
||||||
|
"imap-smtp": "imap-smtp";
|
||||||
|
wecom: "wecom";
|
||||||
|
}>;
|
||||||
|
address: z.ZodOptional<z.ZodString>;
|
||||||
|
receive: z.ZodObject<{
|
||||||
|
ok: z.ZodBoolean;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
latencyMs: z.ZodOptional<z.ZodNumber>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
send: z.ZodObject<{
|
||||||
|
checked: z.ZodBoolean;
|
||||||
|
ok: z.ZodOptional<z.ZodBoolean>;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
note: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailProbeReport = z.infer<typeof mailProbeReportSchema>;
|
||||||
|
export declare const MAIL_DELIVERY_STATES: readonly ["dispatching", "done", "failed", "skipped"];
|
||||||
|
export declare const mailDeliveryStateSchema: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
done: "done";
|
||||||
|
dispatching: "dispatching";
|
||||||
|
skipped: "skipped";
|
||||||
|
}>;
|
||||||
|
export type MailDeliveryState = z.infer<typeof mailDeliveryStateSchema>;
|
||||||
|
export declare const mailDeliveryViewSchema: z.ZodObject<{
|
||||||
|
subscriber: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
done: "done";
|
||||||
|
dispatching: "dispatching";
|
||||||
|
skipped: "skipped";
|
||||||
|
}>;
|
||||||
|
error: z.ZodNullable<z.ZodString>;
|
||||||
|
startedAt: z.ZodISODateTime;
|
||||||
|
finishedAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailDeliveryView = z.infer<typeof mailDeliveryViewSchema>;
|
||||||
|
export declare const mailAddressSchema: z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailAddress = z.infer<typeof mailAddressSchema>;
|
||||||
|
export declare const mailInboundAttachmentSchema: z.ZodObject<{
|
||||||
|
index: z.ZodNumber;
|
||||||
|
name: z.ZodString;
|
||||||
|
contentType: z.ZodString;
|
||||||
|
size: z.ZodNumber;
|
||||||
|
ref: z.ZodNullable<z.ZodString>;
|
||||||
|
note: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailInboundAttachment = z.infer<typeof mailInboundAttachmentSchema>;
|
||||||
|
export declare const mailInboundSummarySchema: z.ZodObject<{
|
||||||
|
id: z.ZodString;
|
||||||
|
accountId: z.ZodString;
|
||||||
|
messageId: z.ZodString;
|
||||||
|
from: z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
to: z.ZodArray<z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
cc: z.ZodArray<z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
subject: z.ZodString;
|
||||||
|
sentAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
receivedAt: z.ZodISODateTime;
|
||||||
|
textPreview: z.ZodString;
|
||||||
|
hasHtml: z.ZodBoolean;
|
||||||
|
attachments: z.ZodArray<z.ZodObject<{
|
||||||
|
index: z.ZodNumber;
|
||||||
|
name: z.ZodString;
|
||||||
|
contentType: z.ZodString;
|
||||||
|
size: z.ZodNumber;
|
||||||
|
ref: z.ZodNullable<z.ZodString>;
|
||||||
|
note: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
deliveries: z.ZodArray<z.ZodObject<{
|
||||||
|
subscriber: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
done: "done";
|
||||||
|
dispatching: "dispatching";
|
||||||
|
skipped: "skipped";
|
||||||
|
}>;
|
||||||
|
error: z.ZodNullable<z.ZodString>;
|
||||||
|
startedAt: z.ZodISODateTime;
|
||||||
|
finishedAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailInboundSummary = z.infer<typeof mailInboundSummarySchema>;
|
||||||
|
export declare const mailInboundDetailSchema: z.ZodObject<{
|
||||||
|
id: z.ZodString;
|
||||||
|
accountId: z.ZodString;
|
||||||
|
messageId: z.ZodString;
|
||||||
|
from: z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
to: z.ZodArray<z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
cc: z.ZodArray<z.ZodObject<{
|
||||||
|
address: z.ZodString;
|
||||||
|
name: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
subject: z.ZodString;
|
||||||
|
sentAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
receivedAt: z.ZodISODateTime;
|
||||||
|
textPreview: z.ZodString;
|
||||||
|
hasHtml: z.ZodBoolean;
|
||||||
|
attachments: z.ZodArray<z.ZodObject<{
|
||||||
|
index: z.ZodNumber;
|
||||||
|
name: z.ZodString;
|
||||||
|
contentType: z.ZodString;
|
||||||
|
size: z.ZodNumber;
|
||||||
|
ref: z.ZodNullable<z.ZodString>;
|
||||||
|
note: z.ZodString;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
deliveries: z.ZodArray<z.ZodObject<{
|
||||||
|
subscriber: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
done: "done";
|
||||||
|
dispatching: "dispatching";
|
||||||
|
skipped: "skipped";
|
||||||
|
}>;
|
||||||
|
error: z.ZodNullable<z.ZodString>;
|
||||||
|
startedAt: z.ZodISODateTime;
|
||||||
|
finishedAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
}, z.core.$strip>>;
|
||||||
|
inReplyTo: z.ZodNullable<z.ZodString>;
|
||||||
|
references: z.ZodArray<z.ZodString>;
|
||||||
|
text: z.ZodNullable<z.ZodString>;
|
||||||
|
html: z.ZodNullable<z.ZodString>;
|
||||||
|
size: z.ZodNumber;
|
||||||
|
rawAvailable: z.ZodBoolean;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailInboundDetail = z.infer<typeof mailInboundDetailSchema>;
|
||||||
|
export declare const mailSendInputSchema: z.ZodObject<{
|
||||||
|
accountId: z.ZodOptional<z.ZodString>;
|
||||||
|
from: z.ZodOptional<z.ZodString>;
|
||||||
|
to: z.ZodArray<z.ZodEmail>;
|
||||||
|
cc: z.ZodDefault<z.ZodArray<z.ZodEmail>>;
|
||||||
|
bcc: z.ZodDefault<z.ZodArray<z.ZodEmail>>;
|
||||||
|
subject: z.ZodString;
|
||||||
|
text: z.ZodOptional<z.ZodString>;
|
||||||
|
html: z.ZodOptional<z.ZodString>;
|
||||||
|
inReplyTo: z.ZodOptional<z.ZodString>;
|
||||||
|
references: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
||||||
|
attachments: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
||||||
|
name: z.ZodString;
|
||||||
|
contentType: z.ZodDefault<z.ZodString>;
|
||||||
|
dataBase64: z.ZodOptional<z.ZodString>;
|
||||||
|
uploadRef: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>>>;
|
||||||
|
idempotencyKey: z.ZodOptional<z.ZodString>;
|
||||||
|
requestedBy: z.ZodObject<{
|
||||||
|
kind: z.ZodEnum<{
|
||||||
|
channel: "channel";
|
||||||
|
tool: "tool";
|
||||||
|
ops: "ops";
|
||||||
|
}>;
|
||||||
|
taskId: z.ZodOptional<z.ZodString>;
|
||||||
|
staffId: z.ZodOptional<z.ZodString>;
|
||||||
|
tool: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailSendInput = z.infer<typeof mailSendInputSchema>;
|
||||||
|
export declare const MAIL_OUTBOUND_STATES: readonly ["sending", "sent", "failed"];
|
||||||
|
export declare const mailOutboundStateSchema: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
sending: "sending";
|
||||||
|
sent: "sent";
|
||||||
|
}>;
|
||||||
|
export type MailOutboundState = z.infer<typeof mailOutboundStateSchema>;
|
||||||
|
export declare const mailSendResultSchema: z.ZodObject<{
|
||||||
|
outboundId: z.ZodString;
|
||||||
|
accountId: z.ZodString;
|
||||||
|
messageId: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
sent: "sent";
|
||||||
|
}>;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
providerRef: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailSendResult = z.infer<typeof mailSendResultSchema>;
|
||||||
|
export declare const mailOutboundViewSchema: z.ZodObject<{
|
||||||
|
id: z.ZodString;
|
||||||
|
accountId: z.ZodString;
|
||||||
|
messageId: z.ZodString;
|
||||||
|
to: z.ZodArray<z.ZodString>;
|
||||||
|
cc: z.ZodArray<z.ZodString>;
|
||||||
|
bcc: z.ZodArray<z.ZodString>;
|
||||||
|
subject: z.ZodString;
|
||||||
|
state: z.ZodEnum<{
|
||||||
|
failed: "failed";
|
||||||
|
sending: "sending";
|
||||||
|
sent: "sent";
|
||||||
|
}>;
|
||||||
|
error: z.ZodNullable<z.ZodString>;
|
||||||
|
providerRef: z.ZodNullable<z.ZodString>;
|
||||||
|
requestedBy: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
||||||
|
createdAt: z.ZodISODateTime;
|
||||||
|
sentAt: z.ZodNullable<z.ZodISODateTime>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailOutboundView = z.infer<typeof mailOutboundViewSchema>;
|
||||||
|
export declare const mailPollOutcomeSchema: z.ZodObject<{
|
||||||
|
accountId: z.ZodString;
|
||||||
|
pulled: z.ZodNumber;
|
||||||
|
stored: z.ZodNumber;
|
||||||
|
dispatched: z.ZodNumber;
|
||||||
|
more: z.ZodBoolean;
|
||||||
|
error: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
export type MailPollOutcome = z.infer<typeof mailPollOutcomeSchema>;
|
||||||
@@ -48,21 +48,60 @@ const HISTORY_TOOL = {
|
|||||||
connectionKeys: ['endpoint'],
|
connectionKeys: ['endpoint'],
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 出站邮件 —— **写类**:过程中登记为动作意向,放行后由系统执行落地 */
|
/**
|
||||||
|
* 出站邮件 —— **写类**:过程中登记为动作意向,放行后由系统执行落地;真发经 mail 模块(ADR-013)。
|
||||||
|
*
|
||||||
|
* 2.0.0(2026-08-25):`to` 改数组,加 cc / bcc / html / attachments(uploadRef)/ inReplyTo / accountId / dryRun。
|
||||||
|
* 契约演进只增不删:v1 的 `body`(纯文本正文)与出参 `queued` 保留。
|
||||||
|
* `connectionKeys: ['account']` —— 从哪个信箱发:'default' = 默认账号,或某个账号 id。
|
||||||
|
* `dryRun`:注册的试运行会**真调处理器**,样例带 dryRun 只校验不发。
|
||||||
|
*/
|
||||||
const MAIL_TOOL = {
|
const MAIL_TOOL = {
|
||||||
...HISTORY_TOOL,
|
...HISTORY_TOOL,
|
||||||
name: 'mail.send',
|
name: 'mail.send',
|
||||||
displayName: '发送邮件',
|
displayName: '发送邮件',
|
||||||
description: '发出邮件(报价单 / 询价函 / 催办):写类动作,放行时才真正触达世界',
|
version: '2.0.0',
|
||||||
|
description:
|
||||||
|
'从系统配置的邮箱发出邮件(报价单 / 询价函 / 催办):写类动作,放行时才真正触达世界。' +
|
||||||
|
'to 是收件人数组;正文给 body(纯文本)或 html,至少一个;附件给上传件的 uploadRef;回信带 inReplyTo(来信的 Message-ID)。',
|
||||||
input: {
|
input: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['to', 'subject'],
|
required: ['to', 'subject'],
|
||||||
properties: { to: { type: 'string' }, subject: { type: 'string' }, body: { type: 'string' } },
|
properties: {
|
||||||
|
to: { type: 'array', items: { type: 'string' }, description: '收件人邮箱' },
|
||||||
|
cc: { type: 'array', items: { type: 'string' }, description: '抄送' },
|
||||||
|
bcc: { type: 'array', items: { type: 'string' }, description: '密送' },
|
||||||
|
subject: { type: 'string' },
|
||||||
|
body: { type: 'string', description: '纯文本正文(与 html 至少一个)' },
|
||||||
|
html: { type: 'string', description: 'HTML 正文' },
|
||||||
|
attachments: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'object', required: ['uploadRef'], properties: { uploadRef: { type: 'string' }, name: { type: 'string' } } },
|
||||||
|
description: '附件:上传件的 ref',
|
||||||
|
},
|
||||||
|
inReplyTo: { type: 'string', description: '回信时带上来信的 Message-ID,收件人那边才能串成一个线程' },
|
||||||
|
accountId: { type: 'string', description: '指定发件账号;缺省用连接配置里的账号 / 默认账号' },
|
||||||
|
dryRun: { type: 'boolean', description: '只校验不发(注册试运行用)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
queued: { type: 'boolean', description: 'v1 遗留:真发时恒 true' },
|
||||||
|
outboundId: { type: 'string' },
|
||||||
|
messageId: { type: 'string' },
|
||||||
|
state: { type: 'string' },
|
||||||
|
dryRun: { type: 'boolean' },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
output: { type: 'object', properties: { queued: { type: 'boolean' } } },
|
|
||||||
effect: 'write' as const,
|
effect: 'write' as const,
|
||||||
|
errors: [
|
||||||
|
{ code: 'send-failed', retryable: true, message: '发信失败(厂商原话见 message)' },
|
||||||
|
{ code: 'no-account', retryable: false, message: '没有可用的邮箱账号' },
|
||||||
|
{ code: 'invalid', retryable: false, message: '入参不合法' },
|
||||||
|
],
|
||||||
idempotent: true,
|
idempotent: true,
|
||||||
connectionKeys: ['endpoint'],
|
connectionKeys: ['account'],
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -297,8 +336,10 @@ export async function seedMvp(container: Container): Promise<void> {
|
|||||||
// 样例集一份喂四件工具:并集覆盖各家必填
|
// 样例集一份喂四件工具:并集覆盖各家必填
|
||||||
input: {
|
input: {
|
||||||
pn: 'LM358DR',
|
pn: 'LM358DR',
|
||||||
to: 'x@y.com',
|
to: ['x@y.com'],
|
||||||
subject: '询价',
|
subject: '询价',
|
||||||
|
body: '样例正文',
|
||||||
|
dryRun: true,
|
||||||
history_days: 30,
|
history_days: 30,
|
||||||
market_stable: true,
|
market_stable: true,
|
||||||
lifecycle: '在产',
|
lifecycle: '在产',
|
||||||
@@ -306,8 +347,10 @@ export async function seedMvp(container: Container): Promise<void> {
|
|||||||
expectShape: { type: 'object' },
|
expectShape: { type: 'object' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// 占位工具是纯工具,没有连接目标 —— 与真工具同门过两审,但不给它端点
|
// 占位工具是纯工具,没有连接目标 —— 与真工具同门过两审,但不给它端点;mail.send 的连接目标是「从哪个信箱发」
|
||||||
...(contract.connectionKeys.length === 0 ? {} : { connectionValues: { endpoint: 'replay://crm' } }),
|
...(contract.connectionKeys.length === 0
|
||||||
|
? {}
|
||||||
|
: { connectionValues: Object.fromEntries(contract.connectionKeys.map((k) => [k, k === 'account' ? 'default' : 'replay://crm'])) }),
|
||||||
});
|
});
|
||||||
if (!verdict.accepted) {
|
if (!verdict.accepted) {
|
||||||
log(' 工具 ' + contract.name + ' 未过两审:' + verdict.staticFindings.map((f) => f.message).join('; '));
|
log(' 工具 ' + contract.name + ' 未过两审:' + verdict.staticFindings.map((f) => f.message).join('; '));
|
||||||
|
|||||||
@@ -138,3 +138,20 @@ describe('staticAudit —— 注册两审之静态审', () => {
|
|||||||
expect(auditPassed(findings)).toBe(true);
|
expect(auditPassed(findings)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('mail.send 2.0.0 的演进', () => {
|
||||||
|
it('保留 v1 的 body / queued、不新增必填 —— 演进检查零违规', () => {
|
||||||
|
const v1 = {
|
||||||
|
name: 'mail.send', displayName: '发送邮件', version: '1.0.0', description: '发出邮件(报价单 / 询价函 / 催办)',
|
||||||
|
input: { type: 'object', required: ['to', 'subject'], properties: { to: { type: 'string' }, subject: { type: 'string' }, body: { type: 'string' } } },
|
||||||
|
output: { type: 'object', properties: { queued: { type: 'boolean' } } },
|
||||||
|
effect: 'write' as const, errors: [], idempotent: true, timeoutMs: 10_000, retries: 2, connectionKeys: ['endpoint'],
|
||||||
|
};
|
||||||
|
const v2 = {
|
||||||
|
...v1, version: '2.0.0', connectionKeys: ['account'],
|
||||||
|
input: { type: 'object', required: ['to', 'subject'], properties: { ...v1.input.properties, to: { type: 'array', items: { type: 'string' } }, html: { type: 'string' }, dryRun: { type: 'boolean' } } },
|
||||||
|
output: { type: 'object', properties: { ...v1.output.properties, outboundId: { type: 'string' }, state: { type: 'string' } } },
|
||||||
|
};
|
||||||
|
expect(checkEvolution(v1, v2)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user