@ broberg/ 邮件核心
标记 HTML 电子邮件 外壳 + 原始 broberg.ai 车队——邮件堆的第一层(视觉结构)。 从sannyandersen的手卷外壳中归纳出,与xrt81/cardmem/cms自己的品牌模板相交叉核对.
npm i @broberg/mail-core # exact-pin for prod-auth deps
不是这个软件包 : 发送 ( ) @broberg/mail)或模板内容/每租户覆盖分辨率(-) @broberg/mail-templates, F040 (英语). 这个软件包只把品牌参数+正文 HTML 变成一个完整的,电子邮件客户端安全文档.
import { renderShell, heading, paragraph, cta, factBox, signOff, makeLogoAttachment } from "@broberg/mail-core";
const html = renderShell({
accentColor: "#2E6B62", // required — no fleet-wide default, nothing silently branded
cardBg: "#FAF9F6", // optional, default "#ffffff"
subject: "Welcome to X",
preheader: "Glad to have you",
logoUrl: "cid:logo", // pair with makeLogoAttachment for inline CID logos
bodyHtml: [
heading("Welcome!"),
paragraph("Thanks for signing up."),
factBox([{ label: "Plan", value: "Pro" }, { label: "Started", value: "2026-07-02" }]),
cta("https://example.com/start", "Get started", { accentColor: "#2E6B62" }),
signOff("Talk soon,", "The team", "— X"),
].join(""),
footerLines: ["Acme Inc · Some Street 1"],
});
密码和魔法链接邮件 : codeMail() (自0.1.0起)
一个一次性代码的共享邮件,可选择使用魔法链接——因此每个应用程序都得到相同的邮件,Gmail/iOS获得最佳机会自行提供"Kopiér kode"/自動填充. 邮件本身无法携带副本按钮( 邮件客户端不运行脚本); 客户端在识别代码时绘制 .
import { codeMail } from "@broberg/mail-core";
const m = codeMail({ accentColor: "#0f7391", logoWidth: 56, product: "cardmem", code: "064562", minutes: 10,
link: "https://app.example/login?t=…" /* optional */, lang: "da" });
// m.subject "Din kode til cardmem: 064562"
// m.html the shell, with the code as one large monospace line
// m.text plain-text part — send it too; the code stands alone on its own line
// m.warnings e.g. another long number beside the code — log it
await mailer.send({ to, subject: m.subject, html: m.html, text: m.text });
它对承认的作用,以及为什么:
- 主题中的“kode”和代码 ——收件箱先读题.
- 代码为TEXT,其字符相接. 数字之间的明显差距是 CSS
letter-spacing从不真实的空格 :0 6 4 5 6 2不再是读者的代码 从来没有像. - 每个邮件一个代码. 措辞中另一个长号在
warnings; 不为4–10个字母/数字的代码. - 一个神奇的链接是一个按钮 和链接被写出 (绝对)
https://只有,否则它扔. - 代码继承了卡片的文字颜色,因此光和暗的卡片保持了外壳的对比.
语出自 CODE_MAIL_TEXT.da 编号 : .en; 覆盖任意一行 text。主题覆盖必须包含代码。 Gmail/iOS是否真的显示该按钮是用真实的收件箱来测量的,在此没有承诺——参见被测量的板上F005.25.
为什么 accentColor 需要,没有默认
每个其它颜色都有明智的中性默认(cardBg #fffe——见下文, textColor 用于对比的自动生成 cardBg, (中文(简体) ), backdropColor 浅灰色)——但 accentColor 无意的 一个默认的口音颜色是一个品牌选择;一个运输会默默地给每个忘记设置的消费者打上品牌. 明确传达出自己的口音.
暗相纸牌工作
textColor 来自 cardBg一种黑暗的光芒 cardBg (例如:. #1a1a1a)默认获得轻文本,无手动 textColor 需要(尽管您仍然可以覆盖它) .
电子邮件客户端兼容性
按表格排列的布局role="presentation" 表格,而不是弹性/网格),内衬样式, prefers-color-scheme + Outlook.com的软件 [data-ogsc] 黑暗模式倒置的守卫. cta() 制作一个表格单元格按钮,从不空出 <a>编号 :<button> 一些客户脱发型了.
Outlook iOS 在暗模式下——测量,方向倒转
以 fd- sundhed (英语) 一个真实的iPhone运行 Outlook iOS (2026-08-19):
- Outlook 映射出一个深色源色到一个光 :
#1a1c2b → #c1c2d1, (中文(简体) ),#4a4d63 → #a7a9bf。要使收件者更能读取一行太光线,使源颜色 较暗."放出"是直觉的修补,使行更差. 同样的警告BrandColors.textColor,所以您的编辑器会显示设置值的位置. - 三种力光技术具有ZERO效应,而不是局部效应:
light only彩色- scheme metas + CSS, ; 彩色- scheme metas + CSS,[data-ogsc]编号 :[data-ogsb]规则,以及#fffffe改为#ffffff。请求#141969,获得#484090。请求#fffffe,获得#484848,而卡和页脚也在同一颜色上着陆,因此页脚完全不再是一区. 他们留在壳里是因为Apple Mail尊重他们. 不要添加期望它修复 Outlook 的第四层 . - 实际翻倍的可辨认性(2.0:1~ 4.9:1)是结构性的:没有中子,结构来自规则和空间而不是填充,没有梯度,以及带有填充和边框的按钮.
eyebrow(), (中文(简体) ), noteBox() 财务报告和审定财务报表 heading({ emphasis }) 存在的原因是 调试器 消费者必须手写 每个原始是可选的:一个带有三行平话的邮件是有效的邮件,所以在这个上所建的模板决不能仅仅因为有一个例子而使一个字段成为需要的.
字体 : fontSerif 财务报告和审定财务报表 fontSans 倒计时
传递一个堆,从来没有一个姓: "'Playfair Display',Georgia,'Times New Roman',serif"Outlook不保证网络fonts,其答案是对LAYER的品牌字体大于安全倒置,而不是去掉. 单名也会去掉Apple Mail的设计,即可以显示它的客户端.
日志附件
这是推荐的标志路线,而不是替代方案. 邮件每次打开都会获取主机标志,持续多年. 如果文件动过,你发送的每封邮件都会丢失其标志,追溯. CID附件在邮件内行走,无法破解。 主机URL是倒计时(logo: { cid, url } 既载又载;又载 data: URI被拒绝是因为Gmail的图像代理把它剥去.
makeLogoAttachment(filePath) 读取一个文件 调用器全程路径 (从未假定) process.cwd(),并返回 resend-形状的内线(CID)附件——或 null 如果文件缺失, 丢失的标志会优雅地降解, 而不是打破交易发送 .
const attachment = makeLogoAttachment("/app/public/brand/logo.png");
if (attachment) {
await resend.emails.send({ ..., attachments: [attachment] });
}
签署:三级,各一轴
signOff([
{ text: "Med venlig hilsen" }, // lead
{ text: "Christian Broberg", tier: "name" }, // + bold
{ text: "CEO & Founding Partner · WebHouse ApS", tier: "meta" }, // + muted colour
]);
每一级都完全改变一个轴 lead —— 说 name 增加重量,
meta 添加了哑的颜色, 并且没有触摸大小 。 这是一个无常 与一个
测试背后,不是品味问题, 这就是为什么没有第四层
来来来来来去:没有第四轴可以花.
有两件事是故意的 没有 ưμ㼯a
name是大胆的,不是大胆的和可爱的. 在真正的调色板上#1a1c2b为 16.86:1 白色和红色#0b0e15是19.29:1——到目前为止,两者都超过了每一阈值,即: 步骤不能被看到。 重量能起作用;颜色相变是装饰.meta没有自己的尺寸. 带有a的分级 相对 大小步调 a 第17/17-bold/15号签入第15/15-bold/13号签入的调色板,底座较小, 13px的副文本 是一个消费者 测量出他们的方式 编号(13.5px)#8486a6在3.5:1时, WCAG 失败 光线 模式 提及了黑暗。 15px是邮件中二级文本的测量地板.
meta'颜色是一个真实的值( y)#4a4d63, 8.29: 1在白话上, 7.54: 1在白话上
默认背景),从不 opacity。 。 。 . 不透明不是低反差
值——对于一个背景来说,它是相对值. opacity:0.65 联 合 国 #1a1c2b
5.29:1 地面保持白色,土地没有测量
; 没有对比工具可以读取, 因为那里
没有颜色可以读取.
遗产形式仍然有效, 字节表示字节
signOff(line1, line2, sign) 与它一贯采取的做法完全相同——断言:
对一个快照 捕获 从运出建筑物, 因为repos称它
在生产邮件中。 它有一个固定轴(大小),它的大槽是 最后一个
参数,这就是数组形式存在的原因:名称-当时的标题签名必须
并让职称比个人大.
一个更正:一个空的 sign 不再发出空白行和空行
<span style="font-size:20px;"></span>。 。 。 .
180px的标志是 有意的共享选择
renderShell 居中标记于 max-width:180px,这是故意的
而不是待决。 一个消费者带着他们自己的40px标记来到旁边
发件人的名字,并把它丢给这个.. "一个共同的表达更值得
"比我们的变体" 录音后,下一个消费者不必问.
但品牌钩 自己保持呼叫者供应. 不要围起来
logoUrl 编号 : accentColor 编号 : fontSerif 在消耗性还原的常量中:
邮件带有 SENDER 的身份, 发送者并不总是重播
模板中. 每端有品牌的共享外壳就是点; a
外壳与品牌烤入 是一个不同的,更糟糕的产品.
品牌值被验证, 并丢出无效的值
accentColor, (中文(简体) ), cardBg, (中文(简体) ), textColor 财务报告和审定财务报表 backdropColor 必须是CSS颜色——
十六进制( x)#rgb, (中文(简体) ), #rrggbb, (中文(简体) ), #rrggbbaa, (中文(简体) ), rgb()编号 :rgba(), (中文(简体) ), hsl()编号 :hsla(),或一个
已命名颜色(全部148, rebeccapurple (包括中文). fontSans 编号 : fontSerif 必须
不包含 < > " 或背书. 还有什么 掷出,命名这个字段.
它拒绝而不是故意逃跑. 一个逃出的非颜色仍然
离开大楼,把垃圾做成文字 style 属性 -
顾客看到一个破损的邮件 没有人看到一个错误。 抛出失败
打电话的人,可以在那里行动.
这很重要 如果你解决品牌 数据库中的每个租户。这些值 被插入到 HTML 属性中, 因此在守卫存在口音之前 颜色
#fff;"></td></tr></table><a href="https://phish.example">Log ind her</a><table><tr><td x="
把锚放进邮件里 没有涉及脚本——登录链接 在原本真实的,正确的品牌交易邮件内是整个 和客户端 脱衣舞剧本仍然制作它。 自行验证 边界;这是最后一行,而不是唯一的一行.
正在装入 Webfont 中 : webfontHref (自0.9.0起)
fontSans: "'DM Sans',Arial,sans-serif" 命名一个家庭。 它没有装入一个。 未安装 DM Sans 的收件人会在堆栈中看到下一个字体 。 在Apple Mail中,一个可以装入webfonts的客户端是Arial.
renderShell({
…,
fontSans: "'DM Sans',-apple-system,'Segoe UI',Arial,sans-serif",
webfontHref: "https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;600&display=swap",
});
换一个 <link rel="stylesheet"> 内 <head>在贝壳之前 <style>。 。 。 .
- 只接受谷歌字体样式表 . 这一定是
https://fonts.googleapis.com/css或者说/css2没有端口和证书 其他任何投出,命名球场. 值会从属性中落地而出,并且可以来自租户数据,所以规则是允许者,而不是逃避(与上面的品牌颜色相同推理). - 展望忽略
<link>Gmail把它剥了. 他们的收受者得到你的堆叠回扣, 所以保持一个真正的堆叠。 不要通过一个姓氏. - 输入=字节与0.8.1相同.
SHELL_VERSION保留3个.
标识大小——设置 logoWidth,并设置它 即使180是你想要的
renderShell({ …, logoUrl, logoWidth: 56 })
关闭它,你得到的历史 中心位置不变(max-width:180px,没有
宽度属性)——与字段存在之前发送的每封邮件的字节相同.
如果你可以的话就设置它. 提供的宽度作为 HTML 发布 width 属性
和在风格 属性是 Outlook 唯一读取的一半 —— 说
它的Word引擎忽略了图像上的 CSS 维度, 因此没有属性
完全画出你的标记 文件 尺寸.
这也是为何缺省仍无保障 释放出一个属性 将修正它给每个人, 并改变每一个现有的 消费者的邮件看起来像在一个客户端,这不是一个可以默默做出的变化.
为什么这个领域存在. 一个消费者发运了480×480马克 - 2×40px 标记,正确的决定——和外壳用520px卡画出180px宽. 你提供的来源越好,结果就越差; 一个 96px 文件会 看起来不错。 这击中了谨慎的消费者,而不是粗心的消费者.
没有 height 属性被发布: 此软件包服务于非平方标志, a
强制正方正方形扭曲它们,精确地表现为尊重属性的客户端.
不创建标识 logoWidth 警告每个过程一次. 它改变否
邮件 - 它在那里,所以你找出是否 资产受到影响,而不是
我们代表你猜测 目前已知:一个消费者的标记是480x480(2xx用于
40px标志,其供应商的正确呼号),因此被打破
展望。 有多少其他人没有被测量, 因为只有你的Repo知道你的文件
宽度。 如果警报火和你的源 低于180px宽,你很好;
如果它更宽,通过 logoWidth。 。 。 .
logoWidth 你怎么样 绘图 标志。 如果你还需要 生产 它在一个
正常尺寸,即 @broberg/media-transform ——互为补充,不是替代.
fill() 越狱 命令很重要
fill(template, vars) // every value HTML-escaped
fillHtml(template, vars) // raw; the unsafe one is the one you have to name
在0. 6.0 之前, 时间, fill() 没有逃脱. vars 按定义是动态的——a
顾客的名字,一个预订参考书, 某某打字—— 所以如果你在
一个更早的版本,然后通过任何用户提供,假设它
以加价方式执行。 按0.5.0计量:
fill("<p>Hej {name}</p>", { name: '<a href="https://phish.example">Log ind</a>' })
→ <p>Hej <a href="https://phish.example">Log ind</a></p>
先放,然后填满.
render THEN fill "Sørensen & Søn" → "Sørensen & Søn" ✓
fill THEN render "Sørensen & Søn" → "Sørensen &amp; Søn" ✗
先让开 {token} 是普通的文本,幸存下来 逃脱未触及,所以
每个值都逃出一次 错误的顺序在最糟糕的情况下失败
方向:适合没有名字的客户 &, (中文(简体) ), < 或引文——大多数
——达到生产看似正确,突破一个真实的人,在
他们的收件箱 没人在看 如果你把两者都叫来,就把它们编成一个
函数,所以调用站点不能错误排序。 (被卡片击中)
在他们自己的模板商店。 )
品牌颜色是SURFACE和TEXT的颜色,很少两者兼而有之
accentColor 上行和上行
cta背景(表面), 财务报告和审定财务报表 眉毛和页脚链接(正文) - 而
cta自己的标签是硬码 #ffffff对于一个碰巧起作用的黑暗品牌.
对于一个轻而易举的,它产生一个没人能读的邮件 无出错:
使,它发送, 它看起来像一个刻意苍白的风格.
在WebHouse黄金上测量 #F7BB2E,然后由 cms 届会报告
Lens对比评论家在一份真实的形式通知中发现:
accent as TEXT on white 1.74:1
accent as TEXT on the footer's #f4f4f5 1.58:1
WHITE label on the accent surface 1.74:1 ← was hardcoded
dark label on the accent surface 10.03:1
WCAG AA wants 4.5:1
没有固定的标签颜色可以正确. 打开 #0f7391 白色措施 5.41和
深3.22; 在金子上,这是完全相反的。 只有这个软件包看到两边
因此,从0.8.0它选择:
| 这里的口音.. | 发生了什么事 |
|---|---|
| 上行、 cta 背景、 边框、 a 表面 | 未触及和你的品牌一样 |
| 目标 标签 | readableInk() ——外壳墨水对比较大 |
| 眉毛,脚跟链接 - 文本 | readableAccent() · 只有在4.5:1以下才变暗(或变淡) |
您可以使用两个输出的助手来进行自己的断言 :
import { contrastRatio, readableInk, readableAccent } from "@broberg/mail-core";
contrastRatio("#ffffff", "#F7BB2E") // 1.74… (null if either is not a hex)
readableInk("#F7BB2E") // "#1a1a1a"
readableAccent("#F7BB2E", "#f4f4f5") // a darker gold that clears AA
readableAccent("#0f7391", "#f4f4f5") // "#0f7391" — already legible, untouched
如果你的牌子已经可以辨认了,就不要动
readableAccent 返回其输入值不变于 4. 5: 1,所以是暗重音
调制 字节相同. 证明对公布的0.7.0 7
形状(贝壳、页脚、标志、cta、眉毛、事实盒、注释盒)——不反对我们
拥有之前的建筑, 这是一种不同的艺术.
SHELL_VERSION 仍然移动到 3,因为输出 已经 a 变动
轻品牌. 这就是标记的意义: 你需要能够告诉"我
模板从"共享外壳已更改"而来.
升级: 在 SAME 中提升您的 shell- version 地板
如果你的密码检查 SHELL_VERSION ——它应该 移动到 3 内
承诺通过0.8.0. cms 在滚回自己的时发现了这个
这是他们报告中最尖锐的事情:
将本地补丁卷回, 将要求留在
>= 2,并降低到 0.7在1.74: 1时给黄金贴上白色标签—— 没有什么失败.
假设外壳3同时接受外壳2的代码是无声炸弹: 用来补偿的工作已经不复存在,需要补偿的版本 唯一的症状是没人看得到的邮件 倒转 地板是改变的,不是改变的.
有两件东西值得偷
勿相与白相为量. 脚上坐着 #f4f4f5。 。 。 . #767676 清除
AA在4.54对白,在4.13对白时失败——所以一个用白作为帮助的助手
一个站立者会运送一个难以辨认的脚链 并通过自己的测试 措施
反面 文本实际上坐.
所觉相相相相相相相相相相相相相相相相相相相相相. #0078fa 读作"黑暗"
BT.601 公式,所以基于亮度的选取选择了白色(4.14)而不是暗墨
(4.20) (中文(简体) ). 将颜色立方体按 5 步擦出 : 14,440个
颜色。 这是一个地区,而不是边缘案例.
SHELL_VERSION 0.6.0之前的价值
SHELL_VERSION 以 <!-- @broberg/mail-core shell vN -->
右后 doc 类型,所以你可以告诉 "我的模板变了" 从 "他们
外壳换了" 通过阅读存储的渲染.
它读 "1" 横跨0.2.x、0.3.x和0.4.x,而渲染改变3
时间 ——页脚规则,页脚文字颜色,卡片背景,
signOff重点 存储 "1" 因此没有证明哪个壳
发了一封信 没错 "2" 从0.6.0开始,现在进行一项测试,比较所提供的
以标记键的固定键进行输出: 改变输出而不发生起伏
版本和构建失败。 两个数字回答两个问题:
已安装的软件包版本.