书籍目录 展开 / 收起

持续触达:邮件、用户引导与召回

用户生成完报告后不一定停在页面上。本节给ClearCopy做一封报告已就绪邮件:用户从邮箱点回报告,继续复制改写。成品包括可直接使用的英文正文和一条发送记录;测试只发给你控制的邮箱,不批量联系别人。

需要你拥有可管理DNS的域名、Resend账户,以及应用中真实可访问的报告页。如果这些还没准备好,先完成邮件草稿;不要填不存在的报告地址就给用户发通知。

先让收件人知道点哪里

复制这封成品,替换大写占位值:

Subject: Your ClearCopy report is ready

Your English app-description check is complete.

We compared your draft with the product facts you supplied.
Open the report to review the suggested rewrite and the claims
that need your attention.

View your report:
REPORT_URL

This check used 1 credit. You have CREDITS_LEFT checks remaining.
If you didn't request this report, contact SUPPORT_EMAIL.

REPORT_URL用已保存报告的实际页面,CREDITS_LEFT用任务完成后的服务端余额,SUPPORT_EMAIL用你能收到回复的地址。邮件本身不放用户的全部原文和模型输入,收件人需要登录相应账户才能看私有报告。任务仅提交还没完成时,不发送complete。

跟着本书的纸面例填写:首个包10次,一次报告保存成功,邮件余额是9;打开报告后能看到那条同步宣称和建议替代句。这样“邮件引导”完成的是一项明确动作,而非把用户拉回首页再让他寻找结果。

在Resend验证真正拥有的发信域名

打开Resend域名说明,页面要求添加并验证自己拥有的域名,还介绍将不同用途放在子域名。配图展示域名入口,关注验证状态,不需要复制官方演示域名。

  1. 在Resend的 Domains 中添加你实际拥有的发信子域名,例如notify.你的域名。
  2. 将页面给出的DNS记录逐条加到域名解析服务,名称和值完整照填;不要使用教程里猜出来的通用TXT值。
  3. 返回Resend等待验证,直到相应记录通过。若一直pending,核对解析服务是否又自动拼接了一次主域名,以及记录类型是否一致。
  4. 从已验证域名选择发件地址,并指定实际支持邮箱作为回复地址。

Gmail发件要求区分所有发件人与大量发件人的要求;其中身份认证是先要处理的基础。使用自己的域名,按邮件服务提供的SPF、DKIM配置与适用DMARC要求完成设置。不要把From伪装成一个自己不拥有的Gmail地址。

Resend官方添加与验证域名入口:使用自己拥有的域名并完成验证,再接发信流程;能添加域名不等于邮件已送达收件箱
Resend官方添加与验证域名入口:使用自己拥有的域名并完成验证,再接发信流程;能添加域名不等于邮件已送达收件箱。

先在本地预览,再给自己发一封

用你自己的测试报告替换占位值后,检查纯文本版本:第一屏看见报告已完成,链接指向正确站点,余额为9。HTML版本可以加一个View report按钮,但同时保留文字链接,不让图片成为唯一入口。

在新的教学目录创建send-report-email.mjs,复制下面的Node.js 22脚本。脚本发送精简的纯文本通知,保留报告链接和登录提示;完整余额文案可在接入真实任务状态后加入。它使用原生fetch,字段按Resend发送邮件API填写;不会将正文、邮箱和密钥输出到日志:

import { pathToFileURL } from 'node:url';

export async function sendReportEmail(env, fetchImpl = fetch) {
  const required = ['RESEND_API_KEY', 'MAIL_FROM', 'MAIL_TO',
    'MAIL_REPLY_TO', 'REPORT_URL', 'NOTIFICATION_ID'];
  if (required.some(key => !env[key])) {
    throw new Error('missing_configuration');
  }
  const reportUrl = new URL(env.REPORT_URL);
  if (reportUrl.protocol !== 'https:') {
    throw new Error('report_url_requires_https');
  }
  const response = await fetchImpl('https://api.resend.com/emails', {
    method: 'POST', redirect: 'error',
    signal: AbortSignal.timeout(10000),
    headers: {
      Authorization: `Bearer ${env.RESEND_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': env.NOTIFICATION_ID
    },
    body: JSON.stringify({
      from: env.MAIL_FROM, to: [env.MAIL_TO],
      reply_to: env.MAIL_REPLY_TO,
      subject: 'Your ClearCopy report is ready',
      text: `Your report is ready.\nView it: ${reportUrl.href}\n` +
        'Sign in with the account that requested this check.'
    })
  });
  if (!response.ok) {
    throw new Error(`email_send_failed_${response.status}`);
  }
  const result = await response.json();
  if (typeof result.id !== 'string' || !result.id) {
    throw new Error('missing_provider_message_id');
  }
  return result.id;
}

if (process.argv[1] &&
    import.meta.url === pathToFileURL(process.argv[1]).href) {
  try {
    const id = await sendReportEmail(process.env);
    console.log('accepted:', id);
  } catch (error) {
    const safe = /^(missing_configuration|report_url_requires_https|email_send_failed_\d+|missing_provider_message_id)$/.test(error.message);
    console.error(safe ? error.message : 'email_request_failed');
    process.exitCode = 1;
  }
}

在同目录创建仅供自己使用的.env文件,加入Git忽略规则,再按实际账户填写下列字段。大写占位内容必须替换,不是真实凭据:

RESEND_API_KEY=RESEND_SERVER_KEY
MAIL_FROM=YOUR_VERIFIED_SENDER
MAIL_TO=YOUR_OWN_CONTROLLED_MAILBOX
MAIL_REPLY_TO=YOUR_SUPPORT_MAILBOX
REPORT_URL=YOUR_REAL_HTTPS_REPORT_URL
NOTIFICATION_ID=report-ready/demo-report-001

MAIL_FROM使用已验证域名的发件地址,MAIL_TO只填你控制的一个邮箱,MAIL_REPLY_TO是能收回复的支持地址;报告URL需要实际存在并要求正确账户登录。服务器密钥不可放入Astro公开变量。准备好并明确要发送这一封测试邮件后,运行:

node --env-file=.env send-report-email.mjs

成功时脚本打印accepted: 平台消息ID;无配置显示missing_configuration,平台拒绝请求时显示HTTP状态编号,不回显请求正文。accepted只说明平台接受,仍要去自己的邮箱查实际投递。本文作者仅用本地mock服务验证请求、重复键、422错误和缺配置分支,没有调用真实发送接口;收件箱测试需要你完成。

对这笔通知建立如下记录,标识为教学示例:

{
  "notificationId": "report-ready-demo-001",
  "userId": "demo-user-a",
  "reportId": "demo-report-001",
  "type": "report_ready",
  "status": "pending",
  "providerMessageId": null,
  "attempts": 0
}

这一次只选你控制的收件地址。成功发送后,保存平台返回的消息标识,再去邮箱确认实际收到、内容完整、链接打开本人报告。平台接受请求和邮箱最终送达是两项不同观察;如果邮件进垃圾箱,也把位置记下来,先检查认证与内容,不立即扩大批量。

阻止重试变成两封相同邮件

假设API发送后连接断了,你不知道平台有没有收下。读取Resend幂等键文档,它说明同一个键可在24小时保留窗口内安全重试。

对这次通知固定使用report-ready/demo-report-001作为幂等键,同次重试保持收件人和内容相同。应用数据库也给(reportId, type)建立唯一记录,并保存平台消息ID。这样超过平台保留窗口后,应用仍能查到已经处理过,不再盲目补发。

完成后的记录变化应是:

情形 应用记录 发送动作
报告刚完成 pending,attempts=0 排入发送任务
请求被平台接受 accepted,保存消息ID 等待投递状态
同任务网络重试 沿用记录和幂等键 不另建一条新通知
查到明确退信 bounced,保存原因 停止重复催发,提示修正邮箱

不要在邮件接口返回200时把状态直接写成“用户已读”。打开跟踪也可能受到邮箱隐私保护影响;本次主要看能否送达和用户是否真正打开报告。

把召回邮件留给愿意接收的人

报告就绪属于用户请求后的通知。另一种“你还有7次未用,回来试试”的邮件是不同任务,不把买过套餐自动解释为接受所有推广。若你要做持续邮件,给用户明确选择和停止入口,并将营销订阅状态单独保存。

可以先准备这一条短提醒草稿,不在本练习中发送:

Subject: Need another check before your next app update?

If you're updating your English app description, you can use
your remaining ClearCopy checks to review the revised draft.
Manage your email preferences: PREFERENCES_URL

完成时回到你自己的收件箱,点击那封报告就绪邮件,看到正确报告与剩余9次。至此邮件有了真实用途:把一次已完成的工作送回用户手中,而不是每天制造一条“保持活跃”的提醒。

登录后可保存阅读进度

下一篇:扩展获客渠道:合作、推荐与小预算广告实验登录保存进度