发布于 2026-01-06 0 阅读
0

创建免费的私有电子邮件 API DEV's Worldwide Show and Tell Challenge 由 Mux 呈现:展示你的项目!

免费创建私有电子邮件 API

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

本文介绍了如何免费创建您自己的安全电子邮件 API,并解释了所有代码。

这个项目的缘起是我想要打造加载速度极快的网站,让全球用户几乎瞬间就能访问(< 30毫秒)。问题在于:

  • 我的每个网站都有一个服务器,用于在联系表格填写完毕后发送电子邮件。
  • 服务器速度慢,而且位于地球上的一个(或两个)地方。
  • 复制服务器管理起来既麻烦又昂贵。

我解决这些问题的策略是:

  • 只需保留前端并将其上传到 CDN:该网站已预先构建完成,完全使用 HTML/CSS/JS,现在即可在全球范围内访问。无需再维护服务器。本文不涉及此主题。
  • 搭建一个独立的邮件API服务器无论何时,只要有人从一个或多个网站填写了联系表单,它都会请求邮件API,然后发送邮件🎉 这就是我们接下来要讨论的内容 :D

👋 了解电子邮件 API

邮件 API 已在 GitHub 上开源,使用 NodeJS 编写:https://github.com/steevepay/email-api。代码非常简洁,只有一个 API 端点POST /send。以下是发送邮件的详细流程:
使用邮件 API 通过 HTTP 请求发送邮件的流程图

  1. 从网站或服务器向电子邮件 API 发出 HTTP 请求。
  2. 每个 HTTP 请求都会经过验证:如果域名来源属于 CORS 白名单,或者在Authorization标头中提供了访问密钥,则接受请求;否则,返回 401 状态代码。
  3. 电子邮件 API 连接到 SMTP 服务器(您选择的服务器),然后发送电子邮件。

获取您的 SMTP 凭据

邮件 API 会连接到 SMTP 服务器来发送邮件,因此您需要 SMTP 凭据。许多 SMTP 服务器都是免费的,请选择您喜欢的服务:

  • Brevo:每天最多发送 300 封电子邮件
  • Smtp2go:每月发送 1,000 封电子邮件
  • Mailjet:每月发送 6000 封电子邮件
  • Google SMTP:如果您订阅了 Google Workspace,则每月可获得一定数量的电子邮件发送配额。
  • OVHCloud:当您购买域名和主机方案时,OVH 会为您提供一个或多个电子邮件地址的免费 SMTP 服务器。
  • Proton Mail:如果您订阅了 Proton,即可访问 SMTP 凭据。
  • 这份清单并不完整,请自行查阅相关资料……

电子邮件 API 设置

在继续操作之前,请确保已安装:Git、NodeJS,以及(可选)Docker。

  • 首先克隆以下代码库:
git clone https://github.com/steevepay/email-api.git
Enter fullscreen mode Exit fullscreen mode
  • 进入email-api目录:
cd email-api
Enter fullscreen mode Exit fullscreen mode
  • 创建一个包含以下配置的 .env 文件,并添加您的 SMTP 凭据:
# SERVER
DOMAIN="mailer.domain.com"
PORT=3000
# SECURITY WHITELIST (OPTIONAL): multiple domains are accepted if it is seperated with a coma, such as `website1.org,website2.org`
WHITELIST="domain.org"
# SECURITY ACCESS KEY (OPTIONAL)
AUTHORIZATION_KEY=""
# SMTP CONFIG
SMTP_HOST=""
SMTP_PORT=""
SMTP_FROM=""
SMTP_PASS=""
Enter fullscreen mode Exit fullscreen mode
  • 使用以下命令安装 NPM 包npm install
  • 启动服务器:
# With NPM Command Line:
npm run start
# With Docker Compose:
docker-compose up
Enter fullscreen mode Exit fullscreen mode

要发送电子邮件,请向端点发出 HTTP 请求POST /send,您必须以 JSON 格式提供以下正文:

{
  "from": "",
  "to": "",
  "subject": "",
  "text": "",
  "html": "",
}
Enter fullscreen mode Exit fullscreen mode

from是邮件发送者,这to是邮件接收者。邮件内容必须以 HTML 和纯文本格式提供。

如果一切顺利,响应将返回OK,状态码为200

如果出现错误,它将返回以下响应:

  • 401:未授权,源域名可能不在 CORS 白名单中,或者您未提供访问密钥。
  • 404:您请求的页面不正确。
  • 405:HTTP 请求正文不是 JSON 格式,或者格式错误,或者缺少属性。
  • 500:服务器出现问题,请检查日志,可能是您的 SMTP 凭据不正确,或者电子邮件未能送达。

堆栈和代码分解

启动 API 只需要三个文件:

index.js
mailer.js
.env
Enter fullscreen mode Exit fullscreen mode

index.js文件中,环境变量被加载,然后我们可以看到由Express提供支持的 API 路由。以下是/send请求:

app.use(express.json());
app.options('/send', cors(corsOptionsDelegate)) // enable pre-flight request for POST request
app.post(
  "/send",
  cors(corsOptionsDelegate),
  body("from").optional().isEmail().normalizeEmail(),
  body("to").optional().isEmail().normalizeEmail(),
  body("cc").optional().isEmail().normalizeEmail(),
  body("subject")
    .optional()
    .not()
    .isEmpty()
    .trim()
    .escape()
    .isLength({ min: 2 }),
  body("html").optional().not().isEmpty().trim().escape().isLength({ min: 2 }),
  body("text").optional().not().isEmpty().trim().escape().isLength({ min: 2 }),
  (req, res, next) => {
    // Finds the validation errors in this request and wraps them in an object with handy functions
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    return next();
  },
  mailer.sendEmail
);
Enter fullscreen mode Exit fullscreen mode

请注意,每个主体参数都使用 Node 模块express-validator进行验证。

CORS npm 模块作为中间件加载,并在每个 HTTP 请求之前检查请求的源域是否在白名单中:

// Configure CORS
var whitelist = process.env?.WHITELIST && process.env?.WHITELIST !== '' ? process.env.WHITELIST?.split(',') : [];
if (whitelist.length > 0) {
    console.log("CORS Whitelist: ", whitelist)
}
var corsOptionsDelegate = function (req, callback) {
  if (whitelist.indexOf(req.header('Origin')) === -1 && req.header('authorization') !== process.env.AUTHORIZATION_KEY) {
    return callback(new Error("Not allowed by CORS"));
  } 
  return callback(null, true);
}
Enter fullscreen mode Exit fullscreen mode

中间件也接受访问密钥,并且必须通过标头提供Authorization

如果请求配置正确,mailer.sendEmail则会执行最后一个中间件来发送电子邮件。该函数mailer.sendEmail位于mailer.js文件中:

const nodemailer = require('nodemailer')

sendEmail = function (req, res) {
  const _cc =  [];

  if (req.body.cc) {
    _cc.push(req.body.cc);
  }
  if (process.env.SMTP_CC) {
    _cc.push(process.env.SMTP_CC);
  }
  if (req.body.from && req.body.to && req.body.subject && req.body.text && req.body.html) {
    if (req.body?.cc) {
      _cc.push(req.body.cc);
    }
    if (process.env.SMTP_CC) {
      _cc.push(process.env.SMTP_CC);
    }
    return deliver({
      from: req.body.from,
      to: req.body.to,
      cc: _cc,
      subject: `${req.body.subject}`,
      text: req.body.text, 
      html: req.body.html
    }, res);
  } else {
    return res.sendStatus(405); // Method Not Allowed
  }
}


function deliver(message, res) {
  const transporter = nodemailer.createTransport({
    host: process.env.SMTP_HOST,
    port: process.env.SMTP_PORT,
    secure: true, // true for 465, false for other ports
    auth: {
      user: process.env.SMTP_FROM,
      pass: process.env.SMTP_PASS
    },
    logger: true
  })

  transporter.sendMail(message, (error, info) => {
    if (error) {
      // eslint-disable-next-line no-console
      console.error("🔴 Send Email Error:", error.toString());
      res.sendStatus(500)
    } else {
      res.sendStatus(200)
    }
  })
}

module.exports = {
  sendEmail
}
Enter fullscreen mode Exit fullscreen mode

Nodemailer模块通过函数连接到 SMTP 服务器nodemailer.createTransport,然后发送邮件transporter.sendMail。函数开始时,会检查邮件的每个参数。如果缺少任何参数,则返回状态码405 。

结论

现在多个网站可以请求同一个 API:该服务是共享的,维护成本低廉且易于维护

需要考虑的主要缺点:

  • 您只能从一个地址发送电子邮件,即与您的 SMTP 凭据关联的地址。
  • 要从新网站发送电子邮件,必须将网站域名添加到 CORS 白名单中,这需要重启才能加载新的环境变量。
  • API 仅支持单个访问密钥,如果要更新密钥,必须编辑文件.env并重启服务器。

感谢阅读!干杯🍻

文章来源:https://dev.to/steeve/create-a-private-email-api-for-free-353m