带附件的账单
给事务邮件附上一份 PDF,并清楚大小上限在哪里。
附件放在同一个 POST /emails 请求里,base64 编码。每个附件是文件名、编码后的字节和内容类型;邮件会被组装成标准的 MIME multipart,并和其他邮件一样签名。
用 curl
把文件编码成一行——解码器不接受 base64 中间的换行,而 GNU base64 默认每 76 个字符换一行。去掉换行的写法在 Linux 和 macOS 上都一样。
PDF=$(base64 < invoice-1042.pdf | tr -d '\n')
curl -X POST https://api.rovela.dev/emails \
-H "Authorization: Bearer re_your_sending_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-1042" \
-d @- <<EOF
{
"from": "Acme 账务 <billing@mail.acme.com>",
"to": "accounts@customer.com",
"subject": "Acme 账单 INV-1042",
"html": "<p>你好,</p><p>附件是你 9 月的账单。应付金额:<strong>¥3,480.00</strong>,请于 10 月 15 日前支付。</p>",
"text": "你好,\n\n附件是你 9 月的账单。应付金额:¥3,480.00,请于 10 月 15 日前支付。",
"attachments": [
{
"filename": "INV-1042.pdf",
"content": "$PDF",
"content_type": "application/pdf"
}
]
}
EOF不写 content_type 的话,这个部分会以 application/octet-stream 发送,很多客户端会把它显示成未知文件而不是直接预览。记得设置。
用 Node.js
import { readFile } from "node:fs/promises"
export async function sendInvoice(invoice, pdfPath) {
const pdf = await readFile(pdfPath)
const res = await fetch("https://api.rovela.dev/emails", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.ROVELA_API_KEY,
"Content-Type": "application/json",
// 账单号就是天然的幂等键:任务重试也不会重复发账单。
"Idempotency-Key": "invoice-" + invoice.number,
},
body: JSON.stringify({
from: "Acme 账务 <billing@mail.acme.com>",
to: invoice.billingEmail,
reply_to: "billing@acme.com",
subject: "Acme 账单 " + invoice.number,
html: "<p>附件是你的账单 " + invoice.number + "。</p>",
text: "附件是你的账单 " + invoice.number + "。",
attachments: [
{
filename: invoice.number + ".pdf",
content: pdf.toString("base64"),
content_type: "application/pdf",
},
],
}),
})
if (!res.ok) throw new Error("invoice email failed: " + res.status)
return (await res.json()).id
}reply_to 让回信去到一个真正有人看的邮箱,而邮件本身仍然从已验证的发信子域名发出。
大小上限
整个 JSON 请求的上限是 2 MB。base64 会让文件变大约三分之一,所以所有附件加起来大约只能有 1.5 MB。超过上限的请求会直接得到
413,什么都不会发出。普通的账单或收据绰绰有余。更大的东西——报表、导出文件、带扫描页的对账单——放到你自己的存储里,邮件里发链接。这对收件人也更友好:大附件会被复制进它到达的每一个邮箱,而且有些企业邮件网关会去掉或隔离它们。
好习惯
- 关键信息在正文里也写一遍——单号、金额、截止日期。预览窗格和手机通知不会打开 PDF。
- 文件名不要带空格或个人信息;它会出现在每一个客户端里,也会跟着转发出去。
- 账单每张一个请求,不要走
/emails/batch:批量请求共享同一个 2 MB 上限,而且一条无效就会让整批被拒。见 批量与定时发送。