# 我答应它起草,没答应它发出去。 | EasyNet Source: https://easynet.run/cases/problems/approve-before-sending [easynet.run · 全部问题场景](https://easynet.run/cases#problem-scenarios) # 我答应它起草,没答应它发出去。 你让助手起草一封延期说明,准备自己看过再发。它却连同附件直接寄给客户,附件还是昨天的版本。起草、修改和对外承诺连在一个工具里,模型判断“写好了”,就跨过了你原本打算亲自做的决定。 最简单的办法是只让它生成草稿,由你在邮件客户端发送。如果现有审批系统已经能控制发送,就继续使用。需要接入自动发送的理由,应是审批后还有大量重复操作,而不是为了省一次点击,取消这次确认。 可以把准备草稿与发送拆成两个函数。前者返回正文、收件人和附件供你预览;后者只负责提交已经批准的那份内容。EasyRemote 能提供分开的调用入口,但“这份内容已批准”的证明仍需由审批服务保存和检查,不能采用助手自己传来的 approved=true。 批准时要绑定具体版本。确认了正文,却允许之后更换收件人或附件,等于没有确认最终发送物。开发时应让批准记录关联这些内容,一旦修改就重新请你看,并限制批准有效期。发送凭证留在提供方,不因为助手能起草就一起交给它。 还要处理没有收到返回的情况。邮件服务可能已经发出,只是网络超时;再次调用不能默认再寄一封。发送程序应使用渠道支持的去重或状态查询,无法确认时保留“发送结果待核实”,由人到渠道端检查,而不是继续自动重试。 用测试收件人演练未批准、批准后改附件、发送超时三种情况。通过之后,你能保留助手整理内容的效率,也清楚哪一刻真正作出了外部承诺。邮件已经送达的事实不能靠撤销本地批准收回,所以这条界线必须在发送之前守住。 01 生成草稿 正文、收件人、附件一起预览。 02 确认具体版本 任何修改都需要重新确认。 03 发送并核对状态 超时先核实,不盲目重发。 批准绑定具体发送物,而不是一个可复用的true。 识别批准之后的内容变化 同名 report.txt 被换成另一份内容,也必须重新审查。先保存附件字节快照,再绑定收件人、主题、正文和内容摘要;发送程序应消费核对后的快照,不能再按路径读取文件。本例没有真实审批或邮件发送。 本地示例 · python ``` """Local content-binding example, not authenticated approval or a mail sender.""" from dataclasses import dataclass import hashlib import json @dataclass(frozen=True) class Attachment: name: str content: bytes @dataclass(frozen=True) class MessageSnapshot: recipients: tuple[str, ...] subject: str body: str attachments: tuple[Attachment, ...] def prepare(recipients, subject, body, attachments) -> MessageSnapshot: # Copy the attachment bytes now, not after the reviewer approves. return MessageSnapshot(tuple(recipients), subject, body, tuple(Attachment(name, bytes(content)) for name, content in attachments)) def fingerprint(snapshot: MessageSnapshot) -> str: content = { "recipients": snapshot.recipients, "subject": snapshot.subject, "body": snapshot.body, "attachments": [{"name": attachment.name, "bytes": len(attachment.content), "sha256": hashlib.sha256(attachment.content).hexdigest()} for attachment in snapshot.attachments], } return hashlib.sha256(json.dumps(content, sort_keys=True, ensure_ascii=False).encode("utf-8")).hexdigest() def checked_snapshot(snapshot: MessageSnapshot, approved_digest: str) -> MessageSnapshot: # approved_digest is supplied by the approval service, never by the model. if fingerprint(snapshot) != approved_digest: raise PermissionError("review again") return snapshot # A sender must consume these bytes, not reopen a file path. if __name__ == "__main__": first = prepare(["test@example.invalid"], "Draft report", "Please review.", [("report.txt", b"Synthetic draft version 1")]) approved = fingerprint(first) # Simulates recorded content; no human approval occurred. replaced = prepare(first.recipients, first.subject, first.body, [("report.txt", b"Synthetic draft version 2")]) try: checked_snapshot(replaced, approved) except PermissionError as error: print(error) ``` 预期结果:输出review again;附件变化不能沿用旧内容批准。 验证同名附件替换后必须重新批准 英文 AGENTS.md 带你运行四项本地测试。这里验证内容绑定,不会发送邮件,也不冒充真实审批服务。 [下载 AGENTS.md](https://easynet.run/examples/message-approval/AGENTS.md)[snapshot.py](https://easynet.run/examples/message-approval/snapshot.py)[test_snapshot.py](https://easynet.run/examples/message-approval/test_snapshot.py) 阅读完整交接任务 ``` # Keep an approval tied to the content that was reviewed Download snapshot.py and test_snapshot.py beside this attachment using the user's supplied page origin. Do not overwrite existing files. Run `python3 -B -m unittest -v test_snapshot.py` and `python3 -B snapshot.py`. Only synthetic text and the reserved example.invalid address are used; do not send an email or request access to the user's mailbox. The main example replaces the contents of report.txt without renaming it and must print "review again". The tests also bind recipients, subject and body, return the exact approved snapshot when unchanged, and show that changing the original byte buffer does not change the frozen attachment bytes. This is content comparison, not authentication, a signed approval record, mail transport, revocation, single-use approval or duplicate-send prevention. The demo's approved digest simulates a stored decision; it is not proof that a human reviewed anything. In a real workflow, never let the sending caller or model submit its own approved digest as authorization. For a separately approved integration, first identify the existing authenticated approval service and sender. The reviewer must preview the exact snapshot, including recipients and attachment bytes. The approval service owns the decision and its scope; the sender consumes that same checked snapshot rather than rereading mutable file paths. Include every additional effective field the real channel uses (such as Cc, Bcc, HTML or sender identity) in both the preview and approved snapshot before claiming full-message binding. Use non-sensitive test data to verify a same-name replacement, recipient change, revoked approval and uncertain delivery. An unknown delivery result must not trigger a blind resend. Retain existing channel authorization and replay controls. Do not present this local helper as built-in EasyNet approval policy or deploy it as a remote sender. Report local tests separately from actual approval, channel acceptance and recipient delivery. ``` 接入时的检查项 未批准草稿无法触发外发操作。 修改正文或收件人后旧批准不再有效。 重试不会产生重复消息。 源码与接入资料 [函数接入指南](https://easynet.run/docs/connect/function) [设备接入指南](https://easynet.run/docs/connect/runtime)