email-sending
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEmail Sending
邮件发送
Node.js (Nodemailer)
Node.js(Nodemailer)
typescript
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
secure: false, // true for 465
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});
await transporter.sendMail({
from: '"My App" <noreply@myapp.com>',
to: 'user@example.com',
subject: 'Welcome!',
html: '<h1>Welcome</h1><p>Thanks for signing up.</p>',
});typescript
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
secure: false, // true for 465
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});
await transporter.sendMail({
from: '"My App" <noreply@myapp.com>',
to: 'user@example.com',
subject: 'Welcome!',
html: '<h1>Welcome</h1><p>Thanks for signing up.</p>',
});Transactional Email Services
事务性邮件服务
Resend (recommended for simplicity)
Resend(推荐用于简单场景)
typescript
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: 'My App <noreply@myapp.com>',
to: ['user@example.com'],
subject: 'Welcome!',
html: '<h1>Welcome</h1>',
});typescript
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: 'My App <noreply@myapp.com>',
to: ['user@example.com'],
subject: 'Welcome!',
html: '<h1>Welcome</h1>',
});SendGrid
SendGrid
typescript
import sgMail from '@sendgrid/mail';
sgMail.setApiKey(process.env.SENDGRID_API_KEY!);
await sgMail.send({
to: 'user@example.com',
from: 'noreply@myapp.com',
templateId: 'd-abc123', // Dynamic template
dynamicTemplateData: { name: 'John', link: resetUrl },
});typescript
import sgMail from '@sendgrid/mail';
sgMail.setApiKey(process.env.SENDGRID_API_KEY!);
await sgMail.send({
to: 'user@example.com',
from: 'noreply@myapp.com',
templateId: 'd-abc123', // Dynamic template
dynamicTemplateData: { name: 'John', link: resetUrl },
});Amazon SES (via AWS SDK v3)
Amazon SES(通过AWS SDK v3)
typescript
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
const ses = new SESv2Client({ region: 'us-east-1' });
await ses.send(new SendEmailCommand({
FromEmailAddress: 'noreply@myapp.com',
Destination: { ToAddresses: ['user@example.com'] },
Content: {
Simple: {
Subject: { Data: 'Welcome!' },
Body: { Html: { Data: htmlContent } },
},
},
}));typescript
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
const ses = new SESv2Client({ region: 'us-east-1' });
await ses.send(new SendEmailCommand({
FromEmailAddress: 'noreply@myapp.com',
Destination: { ToAddresses: ['user@example.com'] },
Content: {
Simple: {
Subject: { Data: 'Welcome!' },
Body: { Html: { Data: htmlContent } },
},
},
}));Email Templates
邮件模板
React Email (recommended for React projects)
React Email(推荐用于React项目)
tsx
// emails/welcome.tsx
import { Html, Head, Body, Container, Text, Button } from '@react-email/components';
export function WelcomeEmail({ name, url }: { name: string; url: string }) {
return (
<Html>
<Head />
<Body style={{ fontFamily: 'sans-serif' }}>
<Container>
<Text>Welcome, {name}!</Text>
<Button href={url} style={{ background: '#000', color: '#fff', padding: '12px 20px' }}>
Get Started
</Button>
</Container>
</Body>
</Html>
);
}
// Render to HTML string
import { render } from '@react-email/render';
const html = await render(<WelcomeEmail name="John" url="https://app.com" />);tsx
// emails/welcome.tsx
import { Html, Head, Body, Container, Text, Button } from '@react-email/components';
export function WelcomeEmail({ name, url }: { name: string; url: string }) {
return (
<Html>
<Head />
<Body style={{ fontFamily: 'sans-serif' }}>
<Container>
<Text>Welcome, {name}!</Text>
<Button href={url} style={{ background: '#000', color: '#fff', padding: '12px 20px' }}>
Get Started
</Button>
</Container>
</Body>
</Html>
);
}
// Render to HTML string
import { render } from '@react-email/render';
const html = await render(<WelcomeEmail name="John" url="https://app.com" />);MJML (framework-agnostic)
MJML(跨框架通用)
typescript
import mjml2html from 'mjml';
const { html } = mjml2html(`
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>Welcome, {{name}}!</mj-text>
<mj-button href="{{url}}">Get Started</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`);typescript
import mjml2html from 'mjml';
const { html } = mjml2html(`
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>Welcome, {{name}}!</mj-text>
<mj-button href="{{url}}">Get Started</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`);Python
Python
python
from email.message import EmailMessage
import aiosmtplib
msg = EmailMessage()
msg["From"] = "noreply@myapp.com"
msg["To"] = "user@example.com"
msg["Subject"] = "Welcome!"
msg.set_content("Welcome!", subtype="html")
await aiosmtplib.send(msg, hostname=SMTP_HOST, port=587,
username=SMTP_USER, password=SMTP_PASS, start_tls=True)python
from email.message import EmailMessage
import aiosmtplib
msg = EmailMessage()
msg["From"] = "noreply@myapp.com"
msg["To"] = "user@example.com"
msg["Subject"] = "Welcome!"
msg.set_content("Welcome!", subtype="html")
await aiosmtplib.send(msg, hostname=SMTP_HOST, port=587,
username=SMTP_USER, password=SMTP_PASS, start_tls=True)Java (Spring Mail)
Java(Spring Mail)
java
@Service
public class EmailService {
@Autowired private JavaMailSender mailSender;
@Autowired private TemplateEngine templateEngine; // Thymeleaf
public void sendWelcome(String to, String name) {
var ctx = new Context();
ctx.setVariable("name", name);
String html = templateEngine.process("welcome", ctx);
var message = mailSender.createMimeMessage();
var helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setFrom("noreply@myapp.com");
helper.setSubject("Welcome!");
helper.setText(html, true);
mailSender.send(message);
}
}java
@Service
public class EmailService {
@Autowired private JavaMailSender mailSender;
@Autowired private TemplateEngine templateEngine; // Thymeleaf
public void sendWelcome(String to, String name) {
var ctx = new Context();
ctx.setVariable("name", name);
String html = templateEngine.process("welcome", ctx);
var message = mailSender.createMimeMessage();
var helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setFrom("noreply@myapp.com");
helper.setSubject("Welcome!");
helper.setText(html, true);
mailSender.send(message);
}
}Anti-Patterns
反模式
| Anti-Pattern | Fix |
|---|---|
| Sending email in request handler | Use background job queue (BullMQ, Celery) |
| No retry on transient failure | Retry with exponential backoff (3 attempts) |
| Hardcoded from address | Use env var, match verified domain |
| HTML without plain text fallback | Always include both HTML and text versions |
| No unsubscribe header | Add |
| Sending from unverified domain | Set up SPF, DKIM, DMARC records |
| 反模式 | 修复方案 |
|---|---|
| 在请求处理器中发送邮件 | 使用后台任务队列(BullMQ、Celery) |
| 临时失败时不重试 | 使用指数退避策略重试(3次尝试) |
| 硬编码发件人地址 | 使用环境变量,匹配已验证域名 |
| 仅使用HTML格式而不提供纯文本备选 | 始终同时包含HTML和纯文本版本 |
| 未添加退订头 | 添加 |
| 从未验证域名发送邮件 | 配置SPF、DKIM、DMARC记录 |
Production Checklist
生产环境检查清单
- SPF, DKIM, DMARC DNS records configured
- Domain verified with email provider
- header on marketing emails
List-Unsubscribe - Bounce and complaint handling (webhook)
- Email sending via background queue (not in request)
- Rate limiting to stay within provider limits
- Plain text fallback for all HTML emails
- Template preview/testing before deploy
- 已配置SPF、DKIM、DMARC DNS记录
- 域名已通过邮件服务提供商验证
- 营销邮件已添加头
List-Unsubscribe - 已处理退回和投诉(通过Webhook)
- 通过后台队列发送邮件(而非在请求中发送)
- 已设置速率限制以符合服务提供商的限制
- 所有HTML邮件均提供纯文本备选
- 部署前已预览/测试模板