mcpField
S
ship-email

ship-email

github.com/tushaarmehtaa开发1248 次安装2 周前更新
代码仓库

关于 ship-email

面向开发者的编码技能,用于为现有应用搭建邮件基础设施,包括 Resend 配置、事务邮件模板、用户分组和管理端群发界面。它会先检查项目的技术栈、数据库和身份验证方式,以适配现有应用。

主要功能

  1. 01检查框架、数据库和身份验证
  2. 02配置 Resend 与共享邮件工具
  3. 03创建事务邮件模板
  4. 04按用户分组发送活动邮件
  5. 05构建带身份验证的管理端发送界面
  6. 06添加退订处理和验证步骤

适用场景

  1. 01添加注册欢迎邮件等事务邮件
  2. 02向指定用户群组发送活动邮件
  3. 03避免邮件故障中断应用流程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tushaarmehtaa/tushar-skills --skill ship-email

安装后在 Claude Code 中输入/ship-email确认已加载。

附带文件references/guide.md

详细介绍

搭建完整的邮件基础设施——Resend 配置、事务性模板、用户细分和管理员发送界面。先读取项目,再接入现有的身份验证和数据库。

Resend 集成中三个容易被忽略的问题

  1. 上线前未验证域名。 Resend 默认使用 @resend.dev 发送邮件。邮件可能进入垃圾邮件箱,或被完全拦截。请在 Resend 控制面板中验证域名,并在向真实用户发送邮件前确认 from 地址与已验证的域名匹配。
  2. 邮件发送失败时抛出异常。 如果 sendEmail 抛出异常且未捕获,欢迎邮件发送失败就会导致注册流程崩溃。邮件发送属于即发即弃操作——务必捕获异常,务必返回 { success, error },绝不抛出异常。
  3. 使用 noreply 地址发送邮件,却未设置 reply-to。 用户有问题时会点击回复。如果收件端没有人查看的收件箱,你就会失去这些用户。即使 from 是 noreply@,也要将 replyTo 设置为真实的收件箱地址。

阶段 1:检测项目

开始编写前,先读取代码库:

1.1 技术栈检测
  • 框架:Next.js / Express / FastAPI / 其他?
  • 数据库:Supabase / Prisma / Drizzle / Mongoose / 原始 SQL?
  • 身份验证:哪些字段用于识别用户?(clerk_user_id、email、id)
  • 用户表结构:有哪些字段?(email、name、credits、plan、created_at、last_active_at)
1.2 询问用户

搭建前,先确认:

I'll wire email for your [framework] app with [database].

Quick decisions:

1. What emails do you need? (transactional, campaigns, or both)
2. Sender: From name and from email? (e.g., "Tushar from Bangers Only <[email protected]>")
3. Domain verified in Resend? (yes / need to set it up)

Defaults: welcome email + re-engagement campaign, Resend.

阶段 2:服务提供商配置

安装并配置 Resend
npm install resend

添加到 .env.example:

RESEND_API_KEY=re_xxxxx

创建邮件工具——代码库中的所有邮件都通过它发送:

// lib/email.ts
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

interface SendEmailParams {
  to: string | string[];
  subject: string;
  html: string;
  from?: string;
  replyTo?: string;
}

export async function sendEmail({
  to,
  subject,
  html,
  from = 'Your Name <[email protected]>',
  replyTo,
}: SendEmailParams) {
  try {
    const result = await resend.emails.send({ from, to, subject, html, reply_to: replyTo });
    return { success: true, id: result.data?.id };
  } catch (error) {
    console.error('Email send failed:', error);
    return { success: false, error };
  }
}

邮件工具绝不能抛出异常。 邮件发送失败不应导致主请求流程崩溃。返回 { success, error },由调用方决定如何处理。

域名验证检查清单

告知用户在正式上线前,于 Resend 控制面板中完成以下操作:

[ ] Add MX record to DNS
[ ] Add SPF TXT record: v=spf1 include:resend.com ~all
[ ] Add DKIM TXT records (Resend provides these in dashboard)
[ ] Verify domain status shows "Verified" in Resend dashboard
[ ] Set reply-to to a monitored inbox (not noreply)

如果未验证域名,邮件可能进入垃圾邮件箱,或根本无法发送。

阶段 3:邮件模板

根据代码库中已有的事件创建模板。至少搭建以下模板:

欢迎邮件(用户注册时触发)
// lib/emails/welcome.ts
export function welcomeEmail({ name, ctaUrl }: { name: string; ctaUrl: string }): string {
  return `
    <div style="font-family: sans-serif; max-width: 560px; margin: 0 auto; padding: 40px 20px; color: #111;">
      <p>Hey ${name},</p>
      <p>You're in. Here's what to do first:</p>
      <p>
        <a href="${ctaUrl}"
           style="display: inline-block; background: #000; color: #fff; padding: 12px 24px;
                  text-decoration: none; border-radius: 6px; font-size: 14px;">
          Get started
        </a>
      </p>
      <p>Any questions — just reply to this email.</p>
      <p style="color: #666; font-size: 13px; margin-top: 40px;">— [Your Name]</p>
      <p style="color: #999; font-size: 11px;">
        <a href="{{{unsubscribe_url}}}" style="color: #999;">Unsubscribe</a>
      </p>
    </div>
  `;
}
重新互动邮件(针对 7 天以上未活跃的用户触发)
// lib/emails/reengagement.ts
export function reengagementEmail({ name, daysSinceActive }: { name: string; daysSinceActive: number }): string {
  return `
    <div style="font-family: sans-serif; max-width: 560px; margin: 0 auto; padding: 40px 20px; color: #111;">
      <p>Hey ${name},</p>
      <p>Haven't seen you in ${daysSinceActive} days. [Product] has [one improvement since they last used it].</p>
      <p>
        <a href="[APP_URL]"
           style="display: inline-block; background: #000; color: #fff; padding: 12px 24px;
                  text-decoration: none; border-radius: 6px; font-size: 14px;">
          Pick up where you left off
        </a>
      </p>
      <p style="color: #666; font-size: 13px; margin-top: 40px;">— [Your Name]</p>
      <p style="color: #999; font-size: 11px;">
        <a href="{{{unsubscribe_url}}}" style="color: #999;">Unsubscribe</a>
      </p>
    </div>
  `;
}
模板规则
  • 使用纯文本风格,不要设计复杂的 HTML
  • 每封邮件只包含一个 CTA
  • 由真人署名,不要使用“团队”作为署名
  • 每个营销活动模板都必须包含退订链接
  • 仅使用内联样式,不要使用外部样式表(垃圾邮件过滤器会移除它们)
将欢迎邮件接入注册流程

找到注册 / 身份验证同步端点,并在创建用户后调用 sendEmail:

// After creating the new user:
if (isNewUser) {
  await sendEmail({
    to: user.email,
    subject: 'Welcome',
    html: welcomeEmail({ name: user.name || 'there', ctaUrl: process.env.APP_URL! }),
  });
}

阶段 4:用户细分

读取用户表结构,并根据现有字段生成用户细分:

如果表中有……生成以下细分
credits / usage_count高活跃(前 20%)、活跃(中间 60%)、不活跃(后 20%)
created_at新用户(<7 天)、已建立用户(7-30 天)、资深用户(30 天以上)
plan / subscription_tier免费、付费、已流失
last_active_at活跃(<7 天)、休眠(7-30 天)、已流失(30 天以上)

为存在的每个细分生成实际的 SQL / ORM 查询。基于积分的应用示例如下:

-- Dormant users: used some credits but went quiet
SELECT email, name
FROM users
WHERE (initial_credits - credits) > 0
  AND last_active_at < NOW() - INTERVAL '7 days'
  AND email_unsubscribed = false;

阶段 5:管理员营销活动界面

如果用户要求创建营销活动,则搭建管理员路由:

POST /api/admin/send-campaign
Body: { segment: string, templateId: string }

处理逻辑:

  1. 检查管理员身份验证——绝不能跳过
  2. 查询细分中的用户
  3. 循环发送邮件——逐封捕获错误,单封发送失败时不要中止整个批次
  4. 返回 { sentCount, failedEmails, errors }
// api/admin/send-campaign/route.ts
export async function POST(req: Request) {
  const adminSecret = req.headers.get('x-admin-secret');
  if (adminSecret !== process.env.ADMIN_SECRET) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const { segment, templateId } = await req.json();
  const users = await getUsersInSegment(segment);

  let sentCount = 0;
  const failedEmails: string[] = [];

  for (const user of users) {
    const result = await sendEmail({
      to: user.email,
      subject: getSubjectForTemplate(templateId),
      html: renderTemplate(templateId, user),
    });

    if (result.success) {
      sentCount++;
    } else {
      failedEmails.push(user.email);
    }
  }

  return Response.json({ sentCount, failedEmails });
}

注意速率限制:Resend 免费套餐每天可发送 100 封邮件。付费套餐起价为每月 $20,每天可发送 50,000 封。如果细分用户数超过每日限额,请添加批次延迟或提醒用户。

第 6 阶段:取消订阅机制

每封营销邮件都必须支持正常取消订阅。这不是可选项,既是法律要求,也是邮件送达率的要求。

// lib/unsubscribe.ts
import { createHmac } from 'crypto';

export function generateUnsubToken(email: string): string {
  return createHmac('sha256', process.env.UNSUB_SECRET!)
    .update(email)
    .digest('hex')
    .slice(0, 16);
}

export function unsubUrl(email: string): string {
  const token = generateUnsubToken(email);
  return `${process.env.APP_URL}/unsubscribe?email=${encodeURIComponent(email)}&token=${token}`;
}

如果 users 表中尚不存在该字段,请添加 email_unsubscribed boolean DEFAULT false。取消订阅路由会将其设为 true,所有细分查询都必须过滤掉 email_unsubscribed = false 的记录。

第 7 阶段:验证

Flow 1: Transactional
[ ] User signs up → welcome email arrives in inbox (not spam)
[ ] Email shows from verified domain, not @resend.dev
[ ] Reply-to is a real inbox

Flow 2: Campaigns
[ ] Admin send-campaign endpoint requires auth
[ ] Segment query returns correct users
[ ] Failed individual emails don't abort the batch
[ ] Response shows sentCount and failedEmails

Flow 3: Unsubscribe
[ ] Unsubscribe link in every campaign email
[ ] Clicking it marks user as unsubscribed in DB
[ ] Unsubscribed users excluded from future segments

Flow 4: Edge Cases
[ ] sendEmail never throws — returns { success: false } on failure
[ ] Email sending doesn't block the main signup flow
[ ] RESEND_API_KEY in .env.example (not in code)

重要说明

  • 切勿将电子邮件地址硬编码。 使用环境变量设置发件人和回复地址。
  • 欢迎邮件的发送时机很重要。 在后台发送,不要让注册流程等待邮件送达。
  • 切换到正式 API 密钥之前,先使用 Resend 的测试模式进行测试。
  • 每个事件只发送一封邮件。 不要在同一触发条件下发送多封邮件,用户会注意到。

请参阅 references/guide.md,了解按细分群体编写的邮件内容、主题行公式和速率限制模式。

评论

支持 Markdown