阿里云邮件服务 Node
API 调用
邮件推送 SDK:https://api.aliyun.com/api-tools/sdk/Dm
SDK 凭据配置:https://help.aliyun.com/document_detail/378664.html
需注意,阿里云 API 文档地址是动态的:
单条邮件发送 https://help.aliyun.com/zh/direct-mail/api-dm-2015-11-23-singlesendmail
hono 报错
tsconfig.json 中 module 需要修改为 ESNext
json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"skipLibCheck": true,
"types": ["node"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"outDir": "./dist",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"verbatimModuleSyntax": false
},
"exclude": ["node_modules"]
}CJS Demo
ts
import "dotenv/config";
import Dm20151123, * as $Dm20151123 from "@alicloud/dm20151123";
import OpenApi, * as $OpenApi from "@alicloud/openapi-client";
import Util, * as $Util from "@alicloud/tea-util";
import Credential, * as $Credential from "@alicloud/credentials";
function createClient(): Dm20151123 {
// 工程代码建议使用更安全的无 AK 方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378664.html。
let credential = new Credential(
new $Credential.Config({
type: "access_key",
// 设置accessKeyId值,此处已从环境变量中获取accessKeyId为例。
accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
// 设置accessKeySecret值,此处已从环境变量中获取accessKeySecret为例。
accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
})
);
let config = new $OpenApi.Config({
credential: credential,
});
// Endpoint 请参考 https://api.aliyun.com/product/Dm
config.endpoint = `dm.aliyuncs.com`;
return new Dm20151123(config);
}
async function main(): Promise<void> {
const client = createClient();
const request = new $Dm20151123.SingleSendMailRequest({
accountName: "noreply@mail.q123q.cc",
addressType: 1,
replyToAddress: false,
toAddress: "694666422@qq.com",
subject: "邮件发送测试",
textBody: "测试内容",
});
try {
const resp = await client.singleSendMail(request);
console.log(JSON.stringify(resp, null, 2));
} catch (error: any) {
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// 错误 message
console.log(error.message);
// 诊断地址
console.log(error.data["Recommend"]);
}
}
main();json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"types": ["node"]
},
"exclude": ["node_modules"]
}json
{
"name": "阿里云邮件服务--demo3",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "tsx index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.7.1",
"dependencies": {
"@alicloud/credentials": "^2.4.4",
"@alicloud/dm20151123": "^1.8.1",
"@alicloud/openapi-client": "^0.4.15",
"@alicloud/tea-util": "^1.4.11",
"dotenv": "^17.2.3"
},
"devDependencies": {
"@types/node": "^25.0.3",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}EJS demo
ts
import "dotenv/config";
import { createRequire } from "module";
import { Config as ClientConfig } from "@alicloud/openapi-client";
import type * as CredentialType from "@alicloud/credentials";
import type * as Dm20151123Type from "@alicloud/dm20151123";
const require = createRequire(import.meta.url);
const { default: Credential } = require("@alicloud/credentials");
const {
default: Dm20151123,
SingleSendMailRequest,
} = require("@alicloud/dm20151123");
const credential: CredentialType.default = new Credential({
// 凭证类型。
type: "access_key",
// 设置accessKeyId值,此处已从环境变量中获取accessKeyId为例。
accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
// 设置accessKeySecret值,此处已从环境变量中获取accessKeySecret为例。
accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
} as CredentialType.Config);
const aliClient: Dm20151123Type.default = new Dm20151123(
new ClientConfig({
// Endpoint 请参考 https://api.aliyun.com/product/Dm
endpoint: "dm.aliyuncs.com",
credential,
})
);
export const sendEmail = async (
email: string[],
subject: string,
content: string
) => {
const data = {
accountName: "noreply@mail.q123q.cc",
addressType: 1,
replyToAddress: false,
toAddress: email.join(","),
subject,
// HtmlBody 与 TextBody 需要有一个
textBody: content,
};
// https://help.aliyun.com/zh/direct-mail/api-dm-2015-11-23-singlesendmail
const request: Dm20151123Type.SingleSendMailRequest =
new SingleSendMailRequest(data);
console.log("request >> ", request);
try {
const result = await aliClient.singleSendMail(request);
return result;
} catch (error) {
const { statusCode, data } = error as AliError<EMailData>;
return { statusCode, body: data };
}
};
////
interface AliError<T = any> {
code: string;
data: T;
description: string;
statusCode: number; // 400
requestId: string; // "F6DAA7DC-DAAF-55B1-902C-2C0FDB1AA733"
}
interface EMailData {
Message: string; //"AccountName is mandatory for this action.";
Recommend: string; // "https://api.aliyun.com/troubleshoot?q=MissingAccountName&product=Dm&requestId=91C595A9-25DF-5C44-84D6-735D57B70F2A";
HostId: string; // "dm.aliyuncs.com";
Code: string; //"MissingAccountName";
}json
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"strict": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"types": ["node"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"outDir": "./dist"
},
"exclude": ["node_modules"]
}json
{
"name": "阿里云邮件服务--demo2",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@alicloud/credentials": "^2.4.4",
"@alicloud/dm20151123": "^1.8.1",
"@alicloud/openapi-client": "^0.4.15",
"@alicloud/tea-typescript": "^1.8.0",
"@alicloud/tea-util": "^1.4.11",
"@hono/node-server": "^1.19.6",
"dotenv": "^17.2.3",
"hono": "^4.11.3"
},
"devDependencies": {
"@types/node": "^20.11.17",
"tsx": "^4.7.1",
"typescript": "^5.8.3"
}
}ali-send-email.ts
ts
import "dotenv/config";
import { createRequire } from "module";
import { Config as ClientConfig } from "@alicloud/openapi-client";
import type * as CredentialType from "@alicloud/credentials";
import type * as Dm20151123Type from "@alicloud/dm20151123";
const require = createRequire(import.meta.url);
const { default: Credential } = require("@alicloud/credentials");
const {
default: Dm20151123,
SingleSendMailRequest,
} = require("@alicloud/dm20151123");
const credential: CredentialType.default = new Credential({
// 凭证类型。
type: "access_key",
// 设置accessKeyId值,此处已从环境变量中获取accessKeyId为例。
accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
// 设置accessKeySecret值,此处已从环境变量中获取accessKeySecret为例。
accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
} as CredentialType.Config);
const aliClient: Dm20151123Type.default = new Dm20151123(
new ClientConfig({
// Endpoint 请参考 https://api.aliyun.com/product/Dm
endpoint: "dm.aliyuncs.com",
credential,
})
);
/**
* 发送文本邮件
*
* @param email 收件人邮箱地址
* @param subject 邮件主题
* @param text 邮件内容
* @returns
*/
export async function sendTextEmail(
email: string[],
subject: string,
text: string
) {
// https://help.aliyun.com/zh/direct-mail/api-dm-2015-11-23-singlesendmail
const request = new SingleSendMailRequest(
createEmilRequest(email, subject, { text })
);
return sendEmail(request);
}
/**
* 发送HTML邮件
*
* @param email 收件人邮箱地址
* @param subject 邮件主题
* @param html 邮件内容
* @returns
*/
export async function sendHtmlEmail(
email: string[],
subject: string,
html: string
) {
const request = new SingleSendMailRequest(
createEmilRequest(email, subject, { html })
);
return sendEmail(request);
}
/**
* 发送验证码
* @param email 收件人邮箱地址
* @param code 验证码
* @param config 配置项
* @param config.subject 邮件主题,默认为"验证码"
* @param config.content 验证码前的提示文本,例如"你的xxx验证码为:"
* @param config.validityMinutes 有效期分钟数,默认为10分钟
* @param config.validityText 有效期文本,如果提供则直接使用此文本覆盖整个有效期内容
* @returns
*/
export async function sendVerificationCode(
email: string[],
code: string,
config: {
subject?: string;
content?: string;
validityMinutes?: number;
validityText?: string;
} = {}
) {
// 默认主题
const subject = config.subject || "验证码";
// 默认提示文本
const content = config.content || "你的验证码为:";
// 默认有效期分钟数
const validityMinutes = config.validityMinutes ?? 10;
// 生成有效期文本:如果提供了 validityText,直接使用;否则根据 validityMinutes 生成
let validityMessage: string;
if (config.validityText) {
validityMessage = config.validityText;
} else {
validityMessage = `此验证码有效期为${validityMinutes}分钟,请勿泄露给他人。`;
}
// 构建 HTML 格式的验证码邮件
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
}
.code-container {
background-color: #f4f4f4;
border: 1px solid #ddd;
border-radius: 4px;
padding: 20px;
margin: 20px 0;
text-align: center;
}
.code {
font-size: 32px;
font-weight: bold;
color: #007bff;
letter-spacing: 4px;
margin: 10px 0;
}
.content {
font-size: 16px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="code-container">
<div class="content">${content}</div>
<div class="code">${code}</div>
</div>
<p style="color: #666; font-size: 14px;">${validityMessage}</p>
</body>
</html>
`;
return sendHtmlEmail(email, subject, html);
}
/**
* 发送邮件
*
* @param request 邮件请求
* @returns
*/
async function sendEmail(request: Dm20151123Type.SingleSendMailRequest) {
try {
const result = await aliClient.singleSendMail(request);
return result;
} catch (error) {
const { statusCode, data } = error as AliError<EMailData>;
return { statusCode, body: data };
}
}
// ==============================================================================================================
function createEmilRequest(
email: string[],
subject: string,
content: { text?: string; html?: string }
) {
// https://help.aliyun.com/zh/direct-mail/api-dm-2015-11-23-singlesendmail
return {
accountName: "noreply@mail.q123q.cc",
// fromAlias: "飞燕",
addressType: 1,
replyToAddress: false,
toAddress: email.join(","),
subject,
// HtmlBody 与 TextBody 需要有一个
textBody: content.text,
htmlBody: content.html,
};
}
interface AliError<T = any> {
code: string;
data: T;
description: string;
statusCode: number; // 400
requestId: string; // "F6DAA7DC-DAAF-55B1-902C-2C0FDB1AA733"
}
interface EMailData {
Message: string; //"AccountName is mandatory for this action.";
Recommend: string; // "https://api.aliyun.com/troubleshoot?q=MissingAccountName&product=Dm&requestId=91C595A9-25DF-5C44-84D6-735D57B70F2A";
HostId: string; // "dm.aliyuncs.com";
Code: string; //"MissingAccountName";
}