现代后端开发中,发送富文本邮件已经成为了一个常见且重要的功能。而React Email库的出现,为后端开发者提供了将React组件用作邮件模板的灵活性和便利性。结合Nodemailer这一强大的邮件发送库,我们可以在Nest.js项目中轻松实现富文本邮件的发送。
首先安装所需的依赖
yarn add react-email @react-email/components
yarn add nodemailer @react-email/render
yarn add -D @types/nodemailer
在项目的 tsconfig.json 中配置编译器选项以支持tsx语法
// tsconfig.json
{
"compilerOptions": {
"jsx": "react"
// ...
}
}
在 src 源代码目录下创建存放邮件模板的文件夹及文件
src/emails/index.tsx
import {
Body,
Container,
Column,
Head,
Heading,
Html,
Img,
Link,
Preview,
Row,
Section,
Text,
} from '@react-email/components';
import * as React from 'react';
interface ConfirmEmailProps {
validationCode?: string;
}
const baseUrl = process.env.BASE_URL
? `https://${process.env.BASE_URL}`
: '';
export const ConfirmEmail = ({ validationCode }: ConfirmEmailProps) => (
<Html>
<Head />
<Preview>Confirm your email address</Preview>
<Body style={main}>
<Container style={container}>
<Section style={logoContainer}>
<Img
src={`${baseUrl}/static/slack-logo.png`}
width="120"
height="36"
alt="Slack"
/>
</Section>
<Heading style={h1}>Confirm your email address</Heading>
<Text style={heroText}>
Your confirmation code is below - enter it in your open browser window
and we'll help you get signed in.
</Text>
<Section style={codeBox}>
<Text style={confirmationCodeText}>{validationCode}</Text>
</Section>
<Text style={text}>
If you didn't request this email, there's nothing to worry about, you
can safely ignore it.
</Text>
<Section>
<Row style={footerLogos}>
<Column style={{ width: '66%' }}>
<Img
src={`${baseUrl}/static/slack-logo.png`}
width="120"
height="36"
alt="Slack"
/>
</Column>
<Column>
<Section>
<Row>
<Column>
<Link href="/">
<Img
src={`${baseUrl}/static/slack-twitter.png`}
width="32"
height="32"
alt="Slack"
style={socialMediaIcon}
/>
</Link>
</Column>
<Column>
<Link href="/">
<Img
src={`${baseUrl}/static/slack-facebook.png`}
width="32"
height="32"
alt="Slack"
style={socialMediaIcon}
/>
</Link>
</Column>
<Column>
<Link href="/">
<Img
src={`${baseUrl}/static/slack-linkedin.png`}
width="32"
height="32"
alt="Slack"
style={socialMediaIcon}
/>
</Link>
</Column>
</Row>
</Section>
</Column>
</Row>
</Section>
<Section>
<Link
style={footerLink}
href="https://slackhq.com"
target="_blank"
rel="noopener noreferrer"
>
Our blog
</Link>
|
<Link
style={footerLink}
href="https://slack.com/legal"
target="_blank"
rel="noopener noreferrer"
>
Policies
</Link>
|
<Link
style={footerLink}
href="https://slack.com/help"
target="_blank"
rel="noopener noreferrer"
>
Help center
</Link>
|
<Link
style={footerLink}
href="https://slack.com/community"
target="_blank"
rel="noopener noreferrer"
data-auth="NotApplicable"
data-linkindex="6"
>
Slack Community
</Link>
<Text style={footerText}>
©2022 Slack Technologies, LLC, a Salesforce company. <br />
500 Howard Street, San Francisco, CA 94105, USA <br />
<br />
All rights reserved.
</Text>
</Section>
</Container>
</Body>
</Html>
);
export default ConfirmEmail;
const footerText = {
fontSize: '12px',
color: '#b7b7b7',
lineHeight: '15px',
textAlign: 'left' as const,
marginBottom: '50px',
};
const footerLink = {
color: '#b7b7b7',
textDecoration: 'underline',
};
const footerLogos = {
marginBottom: '32px',
paddingLeft: '8px',
paddingRight: '8px',
width: '100%',
};
const socialMediaIcon = {
display: 'inline',
marginLeft: '32px',
};
const main = {
backgroundColor: '#ffffff',
margin: '0 auto',
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
};
const container = {
margin: '0 auto',
padding: '0px 20px',
};
const logoContainer = {
marginTop: '32px',
};
const h1 = {
color: '#1d1c1d',
fontSize: '36px',
fontWeight: '700',
margin: '30px 0',
padding: '0',
lineHeight: '42px',
};
const heroText = {
fontSize: '20px',
lineHeight: '28px',
marginBottom: '30px',
};
const codeBox = {
background: 'rgb(245, 244, 245)',
borderRadius: '4px',
marginBottom: '30px',
padding: '40px 10px',
};
const confirmationCodeText = {
fontSize: '30px',
textAlign: 'center' as const,
verticalAlign: 'middle',
};
const text = {
color: '#000',
fontSize: '14px',
lineHeight: '24px',
};
为了让 tsx 文件能够被编译,还需要对 nest cli 进行配置
// nest-cli.json
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"builder": {
"type": "swc",
"options": {
"extensions": [".js", ".ts", ".jsx", ".tsx"]
}
},
"typeCheck": true
}
}
创建配置文件
// src/configs/mail.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs('mail', () => ({
transport: {
host: process.env.MAIL_HOST,
port: parseInt(process.env.MAIL_PORT, 10) || 465,
secure: true,
auth: {
user: process.env.MAIL_USERNAME,
pass: process.env.MAIL_PASSWORD,
},
},
defaults: {
from: process.env.MAIL_FROM,
},
}));
创建 MailService 用于发送邮件
// src/core/services/main.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { render } from '@react-email/render';
import * as nodemailer from 'nodemailer';
interface SendMailConfiguration {
to: string;
subject: string;
text?: string;
template: any;
}
@Injectable()
export class MailService {
private transporter: nodemailer.Transporter;
constructor(private readonly config: ConfigService) {
this.transporter = nodemailer.createTransport(
this.config.get('mail.transport'),
this.config.get('mail.defaults'),
);
}
private generateEmail = (template) => {
return render(template);
};
async send({ to, subject, template }: SendMailConfiguration) {
const html = this.generateEmail(template);
return await this.transporter.sendMail({
to,
subject,
html,
});
}
}
当用户注册成功后向其发送邮箱确认验证码
await this.mailService.send({
to: '邮件接收人的邮箱地址',
subject: `Confirm your email address`,
template: Email({ validationCode: await this.util.getCode(6) }),
});
查看收到的邮件