- 移除 TodoItem 中的 priority、created_at 和 updated_at 字段 - 强制每个任务都必须有唯一 id,且由用户负责生成 - 修改合并模式逻辑,merge=true 下保留未提及的旧任务 - 支持已完成和已取消任务重新激活(状态改回 pending 或 in_progress) - 禁止 in_progress 状态退回到 pending,必须标记为 completed 或 cancelled - 优化状态转换校验,允许特定状态间合法切换 - 简化任务变更消息,移除详细的新增/更新/移除统计 - 更新文档和示例,明确 id 必须由用户生成和使用 - 修复和补充测试,增强状态转换和合并模式验证 - 调整任务时间戳生成逻辑,统一使用当前时间及索引 - 该变更提供更合理的任务状态机械及管理模式,提升稳定性和易用性
16 KiB
邮件 HTML 写法指南
前置条件: 先阅读
../../lark-shared/SKILL.md了解通用安全规则。本文档定义 lark-cli mail 写信场景下的 HTML / CSS / URL 写法、LarkSuite mail-editor 原生格式、可复制片段、3 套场景模板。
CRITICAL 邮件是重要的对外交流渠道,请你保证书写语言凝练扼要
CRITICAL 电子邮件的 HTML 不是 Web 开发的 HTML,请你务必遵守本文档中提及的常用邮件格式书写规范
CRITICAL 请务必使用 shortcut 来进行邮件内容编辑 (+send / +draft-create / +reply / +reply-all / +forward)或 +draft-edit 的 body op,严禁自行拼接 EML
你可以参考 官方模板库 ../assets/templates/ — 提供部分场景模板,可供参考
请注意,邮件内容编辑相关的 shortcut 内置 HTML lint 工具,处于安全考虑和格式适配,你输入的 HTML 可能会被自动调整
风格底线
- 邮件标题小于50字: 邮件主题行
--subject应控制在 50 字内,避免超长标题带来理解困难 - 多用列表、表格:不要堆叠过长的文本段落,请擅长使用列表
<ul>/<ol>或分段<p> - 列表书写规则:不要用
<p>一、...</p><p>二、...</p>这种「中文编号 + 段落」的列表样式,"①②③"、"1) 2) 3)的机械写法也请摒弃;请擅长使用列表格式<ul>/<ol>。 - 正文长度自适应:不限制正文长度,但要求首屏要见到关键信息。
格式书写规范
电子邮件的 HTML 受客户端兼容性与安全沙箱约束,跟 Web 浏览器 HTML 不是同一规范体系。下面是飞书邮箱已验证的最纯净、最美观写法,请直接复制使用。
段落
<p>文字</p>
标题
<h1>一级标题(26px,自动加粗)</h1>
<h2>二级标题(22px)</h2>
<h3>三级标题(20px)</h3>
<h4>四级标题(18px)</h4>
加粗
<b>加粗文字</b>
斜体
<i>斜体文字</i>
下划线
<u>下划线文字</u>
删除线
<s>删除文字</s>
字号
<span style="font-size:18px">放大到 18px</span>
字体
<span style="font-family:'Courier New',monospace">等宽字体</span>
文字颜色
<span style="color:rgb(245,74,69)">红色文字</span>
换行
第一行<br>第二行
分隔
<hr>
列表
<!-- 无序列表 -->
<ul><li>项</li></ul>
<!-- 有序列表 -->
<ol><li>条</li></ol>
<!-- 多级列表通用规则(适用于下面两个示例):
- <ul>/<ol> 的直接子节点必须是 <li>,HTML 规范不允许 <ul> 直接套 <ul>
- 子列表必须嵌套在父 <li> 内,不要拆成多个独立 ol/ul 兄弟
- 每级 list-style-type 用不同符号区分层级(disc/circle/square 或 decimal/lower-alpha/lower-roman)
- 子级用 margin-left:24px 视觉缩进 -->
<!-- 多级有序列表(全 ol 三级嵌套:decimal → lower-alpha → lower-roman) -->
<ol data-list-number="true" style="margin:0px;padding-left:0px;list-style-position:inside">
<li class="temp-li number1" data-li-line="true" data-list="number1" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:decimal;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<b><span style="font-family:inherit"><span style="color:rgb(31,35,41)">第一级(decimal)</span></span></b>
<ol data-list-number="true" style="margin:0px 0px 0px 24px;padding-left:0px;list-style-position:inside">
<li class="temp-li number2" data-li-line="true" data-list="number2" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:lower-alpha;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第二级(lower-alpha,缩进 24px)</span></span>
<ol data-list-number="true" style="margin:0px 0px 0px 24px;padding-left:0px;list-style-position:inside">
<li class="temp-li number3" data-li-line="true" data-list="number3" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:lower-roman;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第三级(lower-roman,再缩进 24px)</span></span>
</li>
</ol>
</li>
<li class="temp-li number2" data-li-line="true" data-list="number2" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:lower-alpha;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第二级(同层)</span></span>
</li>
</ol>
</li>
<li class="temp-li number1" data-li-line="true" data-list="number1" data-ol-id="demo-ol" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:decimal;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<b><span style="font-family:inherit"><span style="color:rgb(31,35,41)">第一级(接续编号)</span></span></b>
</li>
</ol>
<!-- 多级无序列表(全 ul 三级嵌套:disc → circle → square) -->
<ul data-list-bullet="true" style="margin:0px;padding-left:0px;list-style-position:inside">
<li class="temp-li bullet1" data-li-line="true" data-list="bullet1" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:disc;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第一级(disc)</span></span>
<ul data-list-bullet="true" style="margin:0px 0px 0px 24px;padding-left:0px;list-style-position:inside">
<li class="temp-li bullet2" data-li-line="true" data-list="bullet2" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:circle;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第二级(circle,缩进 24px)</span></span>
<ul data-list-bullet="true" style="margin:0px 0px 0px 24px;padding-left:0px;list-style-position:inside">
<li class="temp-li bullet3" data-li-line="true" data-list="bullet3" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:square;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第三级(square,再缩进 24px)</span></span>
</li>
</ul>
</li>
<li class="temp-li bullet2" data-li-line="true" data-list="bullet2" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:circle;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第二级(同层)</span></span>
</li>
</ul>
</li>
<li class="temp-li bullet1" data-li-line="true" data-list="bullet1" style="line-height:1.6;margin:4px 0;padding-left:0px;display:list-item;list-style-type:disc;font-family:inherit;font-size:14px;list-style-position:inside" dir="auto">
<span style="font-family:inherit"><span style="color:rgb(31,35,41)">第一级(同层)</span></span>
</li>
</ul>
表格
<table style="border-collapse:collapse">
<thead>
<tr style="background-color:rgb(242,243,245)">
<th rowspan="2" style="border:1px solid rgb(222,224,227);padding:8px;vertical-align:middle">A</th>
<th colspan="2" style="border:1px solid rgb(222,224,227);padding:8px;text-align:center">B</th>
<th rowspan="2" style="border:1px solid rgb(222,224,227);padding:8px;vertical-align:middle">C</th>
</tr>
<tr style="background-color:rgb(242,243,245)">
<th style="border:1px solid rgb(222,224,227);padding:8px">B1</th>
<th style="border:1px solid rgb(222,224,227);padding:8px">B2</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border:1px solid rgb(222,224,227);padding:8px">a1</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">b1-1</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">b2-1</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">c1</td>
</tr>
<tr>
<td style="border:1px solid rgb(222,224,227);padding:8px">a2</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">b1-2</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">b2-2</td>
<td style="border:1px solid rgb(222,224,227);padding:8px">c2</td>
</tr>
</tbody>
</table>
链接
<a href="https://www.larkoffice.com" style="color:rgb(20,86,240);text-decoration:none">链接文字</a>
AT 用户
<a id="at-user-1" href="mailto:user@example.com" style="cursor:pointer;color:rgb(20,86,240);padding:2px;text-decoration:none;border-radius:999em;margin:0px 2px">@姓名</a>
必填字段 id="at-user-N"、mailto: 和姓名文本
引用
<blockquote style="padding-left:12px;color:rgb(100,106,115);border-left:2px solid rgb(187,191,196);margin:0px">引用文字</blockquote>
文字高亮(荧光笔风格)
<span style="background-color:rgb(255,200,220);color:rgb(31,35,41)">关键里程碑</span>
<span style="background-color:rgb(255,225,140);color:rgb(31,35,41)">待跟进</span>
<span style="background-color:rgb(190,230,200);color:rgb(31,35,41)">已完成</span>
文字强调
<b><span style="font-family:inherit"><span style="color:rgb(245,74,69)">红色加粗</span></span></b>
<i><span style="font-family:inherit"><span style="color:rgb(0,0,0)">斜体</span></span></i>
<u><span style="font-family:inherit"><span style="color:rgb(0,0,0)">下划线</span></span></u>
<s><span style="font-family:inherit"><span style="color:rgb(0,0,0)">删除线</span></span></s>
居中 / 左对齐 / 右对齐
<div style="text-align:center">居中</div>
<div style="text-align:left">左对齐(默认)</div>
<div style="text-align:right">右对齐</div>
盒模型
<div style="margin:8px;padding:12px;width:300px">外边距 8px / 内边距 12px / 宽度 300px</div>
边框
<div style="border:1px solid rgb(222,224,227);border-radius:8px;padding:8px">圆角描边</div>
透明
<span style="opacity:0.5">半透明文字</span>
颜色(推荐调色盘)
<!-- 主黑(正文) -->
<span style="color:rgb(31,35,41)">主文本</span>
<!-- 副灰(次要说明 / 时间 / 备注) -->
<span style="color:rgb(100,106,115)">副文本</span>
<!-- 浅灰(三级文本 / 占位) -->
<span style="color:rgb(143,149,158)">浅灰文本</span>
<!-- LarkSuite 蓝(链接 / mention) -->
<span style="color:rgb(20,86,240)">蓝色文字</span>
<!-- LarkSuite 深蓝(重点标题) -->
<span style="color:rgb(36,91,219)">深蓝标题</span>
<!-- 警示红(错误 / 失败 / 红色加粗) -->
<span style="color:rgb(245,74,69)">警示红</span>
<!-- 紧急橙(紧急 / 阻塞 / 环比上升) -->
<span style="color:rgb(255,140,40)">紧急橙</span>
URL scheme
<a href="https://example.com">外链</a>
<a href="mailto:user@example.com">邮件链接</a>
<img src="cid:abc"> <!-- 内嵌图片,配合 --inline 参数 -->
<img src="data:image/png;base64,iVBOR..."> <!-- base64 内嵌图片 -->
官方 HTML 模板
仓库 ../assets/templates/ 内预制了若干场景模板,按 LarkSuite mail-editor 原生格式写好。注意:模板是静态 HTML,没有变量替换能力,AI 需要手工把模板里的样例文本替换成本次邮件的真实内容。
| 文件 | 说明 |
|---|---|
newsletter--weekly-brief.html |
资讯周报 |
weekly--personal-report.html |
工作周报(个人) |
weekly--team-report.html |
工作周报(团队) |
research--market-report.html |
调研报告 |
job-application--resume.html |
简历邮件 |
跟飞书 OAPI 个人邮件模板(mail.user_mailbox.templates)不同——OAPI 模板是用户邮箱里的"我的模板",跨客户端可见;这里是仓库里的静态 HTML 文件,AI 单次套用即可。
AI 套用流程
-
判断是否能用模板 — 看用户当前要写的邮件类型(周报 / 调研 / 简历 / 资讯 / ...)能否对上
../assets/templates/里的某个文件;不匹配就跳过模板,直接按写法规范从零写。 -
Read 整个 HTML — 用 Read 工具完整读取选定的模板文件,理解骨架(章节标题 / 列表层级 / 占位文本 / mention chip / 段落顺序)。
-
替换文本内容 — 把模板里的样例文字换成用户当前邮件的真实内容;保留所有 inline style / class / data-* 等结构性属性不动;列表条目 / 表格行可按需增删;不需要的整段(如「风险」「下周计划」)整段删除即可,不要留空骨架。
-
调写信 shortcut 生成草稿 — 把替换后的 HTML 通过
--body参数交给写信链路(推荐+draft-create先存草稿、用户复核后再+send):lark-cli mail +draft-create --as user \ --to alice@example.com --subject 'Q3 团队周报' \ --body "$(cat skills/lark-mail/assets/templates/weekly--team-report.html)"实际使用时
$(cat ...)可换成 AI 替换文本后写入的本地副本,或直接把替换后的 HTML 字符串作为--body的值。 -
拿到草稿链接给用户复核 — 写信 shortcut 返回
reference字段(草稿打开链接),把它给用户在飞书邮箱 UI 里打开核对,再决定下一步发送 / 编辑。
写信 shortcut 的 lint 返回值
写信链路(+send / +draft-create / +reply / +reply-all / +forward / +draft-edit body op)调用 emlbuilder 之前会强制 lint 净化 HTML,但 默认 envelope 不携带任何 lint 字段(既无 *_count 也无 finding 数组),envelope 保持小巧供 AI 消费。每个写信 shortcut 默认 envelope 的字段集合:
| 字段 | 出现条件 | 说明 |
|---|---|---|
compose_hint |
6 个 shortcut 默认都附 | 固定英文文案,提示 AI / 用户在组合 HTML 前阅读本文 |
draft_edit_hint |
仅 +draft-create 默认附(其他 5 个 shortcut 不附) |
固定英文文案,提示拿到 draft_id 后改稿走 +draft-edit --draft-id <id> 而不是重跑 +draft-create 产生重复草稿 |
draft_id / message_id |
OAPI 写入成功后写回 | +draft-create / +draft-edit 返回 draft_id;+send / +reply / +reply-all / +forward 返回 message_id |
需要看 lint 详情时加 --show-lint-details:
lark-cli mail +draft-create --show-lint-details \
--to alice@example.com --subject 'Hi' --body '<p>正文</p>'
加了 --show-lint-details 后 envelope 同时返回 lint_applied[] / original_blocked[] 两个完整 Finding 数组(每条含 rule_id / severity / tag_or_attr / excerpt / hint),不再返回任何 *_count 字段 —— 调用方需要 count 时直接 len(lint_applied) / len(original_blocked)。默认场景不要加这个 flag,徒增 token 消耗。
如果只是想预览 lint 会怎么改 HTML,建议直接用 +lint-html 命令——它本来就返回完整 warnings[] / errors[] + cleaned_html,比写信链路 --show-lint-details 更清晰。
相关文档
+lint-html用法- 写信 shortcut:
+send/+draft-create/+reply/+reply-all/+forward/+draft-edit