feat: support DKIM (#129)

This commit is contained in:
Dream Hunter
2024-04-15 13:20:22 +08:00
committed by GitHub
parent d73fee2c97
commit eafcf00e5e
9 changed files with 117 additions and 129 deletions

View File

@@ -1,16 +1,13 @@
# 使用 cloudflare 免费服务,搭建临时邮箱
## [English](README_EN.md)
## [查看部署文档](https://temp-mail-docs.awsl.uk)
## [English](https://temp-mail-docs.awsl.uk/en/)
## [CHANGELOG](CHANGELOG.md)
## [在线演示](https://mail.awsl.uk/)
[https://mail.awsl.uk](https://mail.awsl.uk/)
或者 [https://temp-email.dreamhunter2333.xyz](https://temp-email.dreamhunter2333.xyz/)
[Backend](https://temp-email-api.awsl.uk/)
![](https://uptime.aks.awsl.icu/api/badge/10/status)
![](https://uptime.aks.awsl.icu/api/badge/10/uptime)
@@ -34,8 +31,8 @@
</picture>
- [使用 cloudflare 免费服务,搭建临时邮箱](#使用-cloudflare-免费服务搭建临时邮箱)
- [English](#english)
- [查看部署文档](#查看部署文档)
- [English](#english)
- [CHANGELOG](#changelog)
- [在线演示](#在线演示)
- [功能/TODO](#功能todo)
@@ -47,6 +44,7 @@
- [Cloudflare Email Routing](#cloudflare-email-routing)
- [Cloudflare Pages 前端](#cloudflare-pages-前端)
- [配置发送邮件](#配置发送邮件)
- [配置 DKIM](#配置-dkim)
- [参考资料](#参考资料)
## 功能/TODO
@@ -63,6 +61,7 @@
- [x] 增加查看附件功能
- [x] 使用 rust wasm 解析邮件
- [x] 支持发送邮件
- [x] 支持 DKIM
---
@@ -147,6 +146,9 @@ PREFIX = "tmp" # 要处理的邮箱名称前缀
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # 你的域名
JWT_SECRET = "xxx" # 用于生成 jwt 的密钥
BLACK_LIST = "" # 黑名单,用于过滤发件人,逗号分隔
# dkim config
# DKIM_SELECTOR = "mailchannels" # 参考 DKIM 部分 mailchannels._domainkey 的 mailchannels
# DKIM_PRIVATE_KEY = "" # 参考 DKIM 部分 priv_key.txt 的内容
[[d1_databases]]
binding = "DB"
@@ -215,6 +217,29 @@ v=spf1 include:_spf.mx.cloudflare.net include:relay.mailchannels.net ~all
- 此处 worker 域名为后端 api 的域名,比如我部署在 `https://temp-email-api.awsl.uk/`,则填写 `v=mc1 cfid=awsl.uk`
- 如果你的域名是 `https://temp-email-api.xxx.workers.dev`,则填写 `v=mc1 cfid=xxx.workers.dev`
## 配置 DKIM
参考: [Adding-a-DKIM-Signature](https://support.mailchannels.com/hc/en-us/articles/7122849237389-Adding-a-DKIM-Signature)
Creating a DKIM private and public key:
Private key as PEM file and base64 encoded txt file:
```bash
openssl genrsa 2048 | tee priv_key.pem | openssl rsa -outform der | openssl base64 -A > priv_key.txt
```
Public key as DNS record:
```bash
echo -n "v=DKIM1;p=" > pub_key_record.txt && \
openssl rsa -in priv_key.pem -pubout -outform der | openssl base64 -A >> pub_key_record.txt
```
`Cloudflare``DNS` 记录中添加 `TXT` 记录
- `_dmarc`: `v=DMARC1; p=none; adkim=r; aspf=r;`
- `mailchannels._domainkey`: `v=DKIM1; p=<content of the file pub_key_record.txt>`
## 参考资料
- https://developers.cloudflare.com/d1/

View File

@@ -1,115 +0,0 @@
# cloudflare temp email
## [中文](README.md)
[CHANGELOG](CHANGELOG.md)
## [Live Demo](https://mail.awsl.uk/)
[https://mail.awsl.uk](https://mail.awsl.uk/)
This is a temporary email service that uses Cloudflare Workers to create a temporary email address and view the received email in web browser.
## Features
- [x] Cloudflare D1 as a database
- [x] Deploy the front end with Cloudflare Pages
- [x] Deploy the backend with Cloudflare Workers
- [x] Email forwarding using Cloudflare Email Routing
- [x] Use password to login to the previous mailbox again.
- [x] Get Custom Name Email
- [x] Support multiple languages
- [x] Add access authorization, which can be used as a private site
- [x] Add auto reply feature
- [x] Add attachment viewing function
- [x] use rust wasm to parse email
- [x] support send email
![demo](vitepress-docs/docs/public/readme_assets/demo.png)
## Deploy
[Install/Update Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/)
```bash
npm install wrangler -g
git clone https://github.com/dreamhunter2333/cloudflare_temp_email.git
# Switch to the latest tag or the branch you want to deploy. You can also use the main branch directly.
# git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
```
## DB - Cloudflare D1
```bash
# create a database, and copy the output to wrangler.toml in the next step
wrangler d1 create dev
wrangler d1 execute dev --file=db/schema.sql
# schema update, if you have initialized the database before this date, you can execute this command to update
# wrangler d1 execute dev --file=db/2024-01-13-patch.sql
# wrangler d1 execute dev --file=db/2024-04-03-patch.sql
```
![d1](vitepress-docs/docs/public/readme_assets/d1.png)
### Backend - Cloudflare workers
The first deployment will prompt you to create a project. Please fill in `production` for the `production` branch.
```bash
cd worker
pnpm install
# copy wrangler.toml.template to wrangler.toml
# and add your d1 config and these config
# PREFIX = "tmp" - the email create will be like tmp<xxxxx>@DOMAIN
# IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES
# PASSWORDS = ["123", "456"]
# For admin panel, if not set will no allow to access the admin panel
# ADMIN_PASSWORDS = ["123", "456"]
# DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] you domain name
# JWT_SECRET = "xxx"
# BLACK_LIST = ""
cp wrangler.toml.template wrangler.toml
# deploy
pnpm run deploy
```
you can find and test the worker's url in the workers dashboard
![worker](vitepress-docs/docs/public/readme_assets/worker.png)
## Cloudflare Email Routing
Before you can bind an email address to your Worker, you need to enable Email Routing and have at least one verified email address.
enable email route and config email forward catch-all to the worker
![email](vitepress-docs/docs/public/readme_assets/email.png)
### Frontend - Cloudflare pages
The first deployment will prompt you to create a project. Please fill in `production` for the `production` branch.
```bash
cd frontend
pnpm install
# add .env.local and modify VITE_API_BASE to your worker's url
# VITE_API_BASE=https://xxx.xxx.workers.dev - don't put / in the end
cp .env.example .env.local
pnpm build --emptyOutDir
pnpm run deploy
```
![pages](vitepress-docs/docs/public/readme_assets/pages.png)
## Configure sending emails
Find the `SPF` record of `TXT` in the domain name `DNS` record, and add `include:relay.mailchannels.net`
```bash
v=spf1 include:_spf.mx.cloudflare.net include:relay.mailchannels.net ~all
```
Create a new `_mailchannels` record, the type is `TXT`, the content is `v=mc1 cfid=your worker domain name`
- The worker domain name here is the domain name of the back-end api. For example, if I deploy it at `https://temp-email-api.awsl.uk/`, fill in `v=mc1 cfid=awsl.uk`
- If your domain name is `https://temp-email-api.xxx.workers.dev`, fill in `v=mc1 cfid=xxx.workers.dev`

View File

@@ -100,6 +100,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] {
items: [
{ text: '命令行部署准备', link: 'cli/pre-requisite' },
{ text: 'D1 数据库', link: 'cli/d1' },
{ text: '配置 DKIM', link: 'dkim' },
{ text: 'Cloudflare workers 后端', link: 'cli/worker' },
{ text: '配置邮件转发', link: 'email-routing.md' },
{ text: 'Cloudflare Pages 前端', link: 'cli/pages' },
@@ -111,6 +112,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] {
collapsed: false,
items: [
{ text: 'D1 数据库', link: 'ui/d1' },
{ text: '配置 DKIM', link: 'dkim' },
{ text: 'Cloudflare workers 后端', link: 'ui/worker' },
{ text: '配置邮件转发', link: 'email-routing.md' },
{ text: 'Cloudflare Pages 前端', link: 'ui/pages' },

View File

@@ -1,5 +1,7 @@
# cloudflare temp email
This is a temporary email service that uses Cloudflare Workers to create a temporary email address and view the received email in web browser.
## Features
- [x] Cloudflare D1 as a database
@@ -14,6 +16,7 @@
- [x] Add attachment viewing function
- [x] use rust wasm to parse email
- [x] support send email
- [x] support DKIM
## Deploy
@@ -101,3 +104,27 @@ Create a new `_mailchannels` record, the type is `TXT`, the content is `v=mc1 cf
- The worker domain name here is the domain name of the back-end api. For example, if I deploy it at `https://temp-email-api.awsl.uk/`, fill in `v=mc1 cfid=awsl.uk`
- If your domain name is `https://temp-email-api.xxx.workers.dev`, fill in `v=mc1 cfid=xxx.workers.dev`
## Configure DKIM
Ref: [Adding-a-DKIM-Signature](https://support.mailchannels.com/hc/en-us/articles/7122849237389-Adding-a-DKIM-Signature)
Creating a DKIM private and public key:
Private key as PEM file and base64 encoded txt file:
```bash
openssl genrsa 2048 | tee priv_key.pem | openssl rsa -outform der | openssl base64 -A > priv_key.txt
```
Public key as DNS record:
```bash
echo -n "v=DKIM1;p=" > pub_key_record.txt && \
openssl rsa -in priv_key.pem -pubout -outform der | openssl base64 -A >> pub_key_record.txt
```
Add `TXT` record in `Cloudflare` all your mail domain `DNS`
- `_dmarc`: `v=DMARC1; p=none; adkim=r; aspf=r;`
- `mailchannels._domainkey`: `v=DKIM1; p=<content of the file pub_key_record.txt>`

View File

@@ -10,7 +10,7 @@ cp wrangler.toml.template wrangler.toml
## 修改 `wrangler.toml` 配置文件
```bash
```toml
name = "cloudflare_temp_email"
main = "src/worker.js"
compatibility_date = "2023-12-01"
@@ -29,6 +29,9 @@ PREFIX = "tmp" # 要处理的邮箱名称前缀,不需要后缀可配置为空
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # 你的域名, 支持多个域名
JWT_SECRET = "xxx" # 用于生成 jwt 的密钥
BLACK_LIST = "" # 黑名单,用于过滤发件人,逗号分隔
# dkim config
# DKIM_SELECTOR = "mailchannels" # 参考 DKIM 部分 mailchannels._domainkey 的 mailchannels
# DKIM_PRIVATE_KEY = "" # 参考 DKIM 部分 priv_key.txt 的内容
# D1 数据库的名称和 ID 可以在 cloudflare 控制台查看
[[d1_databases]]

View File

@@ -0,0 +1,31 @@
# 配置 DKIM
参考: [Adding-a-DKIM-Signature](https://support.mailchannels.com/hc/en-us/articles/7122849237389-Adding-a-DKIM-Signature)
Creating a DKIM private and public key:
Private key as PEM file and base64 encoded txt file:
```bash
openssl genrsa 2048 | tee priv_key.pem | openssl rsa -outform der | openssl base64 -A > priv_key.txt
```
Public key as DNS record:
```bash
echo -n "v=DKIM1;p=" > pub_key_record.txt && \
openssl rsa -in priv_key.pem -pubout -outform der | openssl base64 -A >> pub_key_record.txt
```
`Cloudflare``DNS` 记录中添加 `TXT` 记录
例如:
- `_dmarc`: `v=DMARC1; p=none; adkim=r; aspf=r;`
- `mailchannels._domainkey`: `v=DKIM1; p=<content of the file pub_key_record.txt>`
那我在 `wrangler.toml` 中的配置应该是这样的:
```toml
DKIM_SELECTOR = "mailchannels"
DKIM_PRIVATE_KEY = "<priv_key.txt 的内容>"
```

View File

@@ -96,7 +96,7 @@ api.get('/admin/mails', async (c) => {
return c.text("Invalid offset", 400)
}
const { results } = await c.env.DB.prepare(
`SELECT id, source, raw, created_at FROM raw_mails where address = ? order by id desc limit ? offset ?`
`SELECT * FROM raw_mails where address = ? order by id desc limit ? offset ?`
).bind(address, limit, offset).all();
let count = 0;
if (offset == 0) {
@@ -120,7 +120,7 @@ api.get('/admin/mails_unknow', async (c) => {
return c.text("Invalid offset", 400)
}
const { results } = await c.env.DB.prepare(`
SELECT id, source, raw, created_at FROM raw_mails
SELECT * FROM raw_mails
where address NOT IN(select concat('${c.env.PREFIX}', name) from address)
order by id desc limit ? offset ? `
).bind(limit, offset).all();

View File

@@ -52,13 +52,22 @@ api.post('/api/send_mail', async (c) => {
if (!content) {
return c.text("Invalid content", 400)
}
const body = JSON.stringify({
let dmikBody = {}
if (c.env.DKIM_SELECTOR && c.env.DKIM_PRIVATE_KEY && address.includes("@")) {
dmikBody = {
"dkim_domain": address.split("@")[1],
"dkim_selector": c.env.DKIM_SELECTOR,
"dkim_private_key": c.env.DKIM_PRIVATE_KEY,
}
}
const body = {
"personalizations": [
{
"to": [{
"email": to_mail,
"name": to_name,
}]
}],
...dmikBody,
}
],
"from": {
@@ -70,13 +79,13 @@ api.post('/api/send_mail', async (c) => {
"type": is_html ? "text/html" : "text/plain",
"value": content,
}],
});
};
let send_request = new Request("https://api.mailchannels.net/tx/v1/send", {
"method": "POST",
"headers": {
"content-type": "application/json",
},
"body": body,
"body": JSON.stringify(body),
});
const resp = await fetch(send_request);
const respText = await resp.text();
@@ -98,9 +107,12 @@ api.post('/api/send_mail', async (c) => {
}
// save to sendbox
try {
if (body?.personalizations?.[0]?.dkim_private_key) {
delete body.personalizations[0].dkim_private_key;
}
const { success: success2 } = await c.env.DB.prepare(
`INSERT INTO sendbox (address, raw) VALUES (?, ?)`
).bind(address, body).run();
).bind(address, JSON.stringify(body)).run();
if (!success2) {
console.warn(`Failed to save to sendbox for ${address}`);
}

View File

@@ -16,6 +16,9 @@ PREFIX = "tmp"
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"]
JWT_SECRET = "xxx"
BLACK_LIST = ""
# dkim config
# DKIM_SELECTOR = ""
# DKIM_PRIVATE_KEY = ""
[[d1_databases]]
binding = "DB"