SuperAPI AI 工具集

smtp-gateway

通用与其它

查看项目

更新于 10-29 11:03

技能介绍

用途

SMTP Gateway 用于让 Internet Computer 上的 canister 接收普通电子邮件。一个链下邮件服务器(SMTP 网关)接受寄给 canister 的邮件,然后像 HTTP 网关调用 http_request 那样,调用 canister 的 smtp_request 方法完成投递。DFINITY 运营的网关负责接收发往 <canister-id>.icp0.io 的邮件,其他运营方也可以基于 ic-gateway 自建网关。canister 只需实现两个方法即可启用收信,无需注册。邮件是单向的:canister 不能通过网关发信,也不会收到退信,因为网关拒绝空发件人(MAIL FROM:<>)。

寻址方式

  • anything@<canister-id>.icp0.io:无需任何设置,每个此类地址已有指向网关的 MX 记录。
  • anything@<custom-domain>:域名必须注册为 IC 自定义域名,且 MX 记录指向网关。子域通过标准方式(CNAME 到 <domain>.icp1.io)可继承网关 MX 记录;顶级域不能做 CNAME,需发布 dig +short MX boundary.dfinity.network 列出的主机作为 MX 记录并保持同步,不能直接发布 boundary.dfinity.network 本身。

网关路由收件人时:如果域名的第一个 DNS 标签能解析为 principal,则该 principal 为 canister;否则在已注册自定义域名中查找精确域名;无匹配返回 550 5.1.1。域名以小写形式到达,本地部分(user)原样保留(大小写、+tag、引号均保留),非 ASCII 本地部分会被网关拒绝。

网关在调用目标 canister 前会请求 https://<canister-id>.icp0.io/.well-known/ic-smtp-canister-id;若返回 2xx 且响应体(去空白)为 principal,则所有 SMTP 调用转给该 principal,否则回退到原地址 canister,结果缓存 10 分钟。适合前端 canister 拥有域名、后端 canister 处理邮件的场景。

协议方法

  • smtp_request_validate : (SmtpRequest) -> (SmtpResponse) query,必需,每个 RCPT TO 调用一次,消息传输前调用,envelope 中只有一个收件人,message 与 message_id 为 null。
  • smtp_request : (SmtpRequest) -> (SmtpResponse),必需,DATA 之后按目标 canister 调用一次,携带所有收件人(每会话最多 10 个)、消息头与原始正文,message_id 为新的 UUIDv7。
  • smtp_capabilities、smtp_upload_chunk、smtp_upload_commit、smtp_upload_status、smtp_upload_abort 为可选方法,用于超过约 2 MiB ingress 限制的分块上传。

类型定义在 candid/smtp_gateway.did,Rust crate 导出这些类型;code 是 nat64,变体标签为 Ok 和 Err,Ok 携带空记录。

回复码即接口

网关根据 SmtpRequestError.code 的类别行动:500–599 为永久失败,其他为临时失败;trap 和调用失败视为临时失败,但方法未导出或请求超出一个 ingress 消息大小属永久失败。

RCPT TO 阶段(smtp_request_validate):Ok 返回 250;Err 550 仅对该收件人返回 550 5.1.2 且消息丢弃;Err 其他 5xx 返回 550 5.1.3;Err 其他码返回 451 4.4.3;方法未导出返回 550 5.1.3;trap 或调用失败返回 451 4.4.3。

DATA 之后(smtp_request):Ok 返回 250 已投递;Err 5xx 返回 550 5.5.0 并给发件人退信;Err 其他码返回 450 4.5.0 稍后重试;trap、调用失败或 90 秒内无回复返回 450 并重试。

建议使用的码:永久——550 无此邮箱(保留专用)、501 缺少必需字段、552 内容/头/收件人列表过大、554 内容被拒、530 调用方未授权;临时——450 稍后重试、452 存储不足。

常见错误与限制

  • 用 trap 代替 Err:trap 被当作临时失败,发件服务器会重试数天。所有拒绝必须返回 Err。
  • 用非 5xx 报告永久问题:只有 500–599 会退信。
  • 对 550 之外的场景使用 550:验证阶段 550 会固定变成「邮箱不存在」且丢弃你的文本,策略拒绝应使用 552、554 等。
  • 省略 smtp_request_validate:网关将每个收件人视为不支持 SMTP 协议而永久拒绝。
  • 在验证方法中要求 message 或 message_id:验证阶段只收到 envelope。
  • 依赖 Candid 校验请求:SmtpRequest 每个字段都是 opt,任何记录都能解码,需自行检查字段并返回 501。
  • 忘记 SMTP 方法是公开的:任何人都能直接调用并绕过网关检查,需检查调用方 principal(例如仅允许 DFINITY 网关 ttvgm-…-oae 和控制器),用 530 拒绝而非 trap,并对输入设上限。
  • 信任发件人地址:DFINITY 网关会拒绝 SPF 失败的邮件和所有 DKIM 签名失败的邮件,但接受无 SPF 记录的发件人和未签名邮件,不检查签名域与 From 对齐(无 DMARC),也不把结果传给 canister。若身份重要,需在 canister 内验证 DKIM。
  • 期待已解码的头部:每个值是从冒号后的原始文本,包含前导空格、行尾、折行和编码词,需自行 trim、解码,头部名大小写不敏感。
  • 期待已解析正文:body 是头部块之后的原始字节,CRLF 行尾,无 MIME 处理。
  • 用 message_id 去重:它标识一次 SMTP 事务而非一封邮件,重试会生成新值,投递为至少一次,需按 RFC 5322 Message-ID 去重或使处理幂等。
  • 在 smtp_request 中 await:网关等待 90 秒,超时视为临时失败并重试,可能导致重复投递;应同步接收并延迟慢操作。
  • 过长的错误消息:会被复制进 SMTP 回复行,RCPT TO 阶段约 224 字节截断,投递回复超过约 220 字节可能被丢弃;应使用短、单行 ASCII。
  • 通过错误码泄露秘密:每个 Err 都会告知调用方信息,5xx 还会退信;对未知或无效内容返回 Ok 并内部记录。
  • 将邮件存储在堆上或把配置存为 stable 变量:堆状态在升级时丢失;Motoko 持久 actor 中普通 let 是稳定的并保留首次安装值,配置应声明为 transient let。

适用场景

适合需要在 IC canister 中接收邮件的应用,例如支持邮箱、联系表单回信、验证码邮件接收等。示例实现仅通过 DFINITY 网关接收 support@ 和 hello@ 的邮件并存储。限制:只能收信不能发信,不能收退信;需自行处理身份验证、输入边界和去重;自定义域名需正确配置 MX 记录。