Skip to content

阿里云邮件服务 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";
}