Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ENABLE_USER_DELETE_EMAIL && ENABLE_AUTO_REPLY && modify fetchAddressError i18n && UI: show autoRefreshInterval #169

Merged
merged 1 commit into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 2 additions & 208 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## [查看部署文档](https://temp-mail-docs.awsl.uk)

## [English](https://temp-mail-docs.awsl.uk/en/)
## [English Docs](https://temp-mail-docs.awsl.uk/en/)

## [CHANGELOG](CHANGELOG.md)

Expand All @@ -21,20 +21,10 @@

- [使用 cloudflare 免费服务,搭建临时邮箱](#使用-cloudflare-免费服务搭建临时邮箱)
- [查看部署文档](#查看部署文档)
- [English](#english)
- [English Docs](#english-docs)
- [CHANGELOG](#changelog)
- [在线演示](#在线演示)
- [功能/TODO](#功能todo)
- [什么是临时邮箱](#什么是临时邮箱)
- [Cloudflare 服务](#cloudflare-服务)
- [wrangler 的安装](#wrangler-的安装)
- [D1 数据库](#d1-数据库)
- [Cloudflare workers 后端](#cloudflare-workers-后端)
- [Cloudflare Email Routing](#cloudflare-email-routing)
- [Cloudflare Pages 前端](#cloudflare-pages-前端)
- [配置发送邮件](#配置发送邮件)
- [配置 DKIM](#配置-dkim)
- [参考资料](#参考资料)

## 功能/TODO

Expand All @@ -51,199 +41,3 @@
- [x] 使用 rust wasm 解析邮件
- [x] 支持发送邮件
- [x] 支持 DKIM

---

## 什么是临时邮箱

临时邮箱,也被称为一次性邮箱或临时邮件地址,是一种用于临时接收邮件的虚拟邮箱。与常规邮箱不同,临时邮箱旨在提供一种匿名且临时的邮件接收解决方案。

临时邮箱往往由网站或在线服务提供商提供,用户可以在需要注册或接收验证邮件时使用临时邮箱地址,而无需暴露自己的真实邮箱地址。这样做的好处是可以保护个人隐私

---

## Cloudflare 服务

- `D1` 是 `Cloudflare` 的原生无服务器数据库。
- `Pages` 是 `Cloudflare` 的静态网站托管服务, 速度超快,始终保持最新状态。
- `Workers` 是 `Cloudflare` 的 `serverless` 应用服务,可以在全球 300 个数据中心运行代码, 而无需配置或维护基础架构。
- `Cloudflare Email Routing` 可以处理域名的所有电子邮件流量,而无需管理电子邮件服务器。

---

## wrangler 的安装

安装 wrangler

```bash
npm install wrangler -g
```

克隆项目

```bash
git clone https://github.com/dreamhunter2333/cloudflare_temp_email.git
# 切换到最新 tag 或者你想部署的分支,你也可以直接使用 main 分支
# git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
```

---

## D1 数据库

第一次执行登录 wrangler 命令时,会提示登录, 按提示操作即可

```bash
# 创建 D1 并执行 schema.sql
wrangler d1 create dev
wrangler d1 execute dev --file=db/schema.sql
# schema 更新,如果你在此日期之前初始化过数据库,可以执行此命令更新
# wrangler d1 execute dev --file=db/2024-01-13-patch.sql
# wrangler d1 execute dev --file=db/2024-04-03-patch.sql
```

创建完成后,我们在 cloudflare 的控制台可以看到 D1 数据库

![D1](vitepress-docs/docs/public/readme_assets/d1.png)

---

## Cloudflare workers 后端

初始化项目

```bash
cd worker
pnpm install
cp wrangler.toml.template wrangler.toml
```

修改 `wrangler.toml` 文件

```bash
name = "cloudflare_temp_email"
main = "src/worker.js"
compatibility_date = "2023-08-14"
node_compat = true

[vars]
PREFIX = "tmp" # 要处理的邮箱名称前缀
# 如果你想要你的网站私有,取消下面的注释,并修改密码
# PASSWORDS = ["123", "456"]
# admin 控制台密码, 不配置则不允许访问控制台
# ADMIN_PASSWORDS = ["123", "456"]
# admin 联系方式,不配置则不显示,可配置任意字符串
# ADMIN_CONTACT = "xx@xx.xxx"
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # 你的域名
JWT_SECRET = "xxx" # 用于生成 jwt 的密钥
BLACK_LIST = "" # 黑名单,用于过滤发件人,逗号分隔
# 默认发送邮件余额,如果不设置,将为 0
# DEFAULT_SEND_BALANCE = 1
# dkim config
# DKIM_SELECTOR = "mailchannels" # 参考 DKIM 部分 mailchannels._domainkey 的 mailchannels
# DKIM_PRIVATE_KEY = "" # 参考 DKIM 部分 priv_key.txt 的内容

[[d1_databases]]
binding = "DB"
database_name = "xxx" # D1 数据库名称
database_id = "xxx" # D1 数据库 ID

# 新建地址限流配置
# [[unsafe.bindings]]
# name = "RATE_LIMITER"
# type = "ratelimit"
# namespace_id = "1001"
# # 10 requests per minute
# simple = { limit = 10, period = 60 }
```

部署

第一次部署会提示创建项目, `production` 分支请填写 `production`

```bash
pnpm run deploy
```

部署成功之后再路由中可以看到 `worker` 的 `url`,控制台也会输出 `worker` 的 `url`

![worker](vitepress-docs/docs/public/readme_assets/worker.png)

---

## Cloudflare Email Routing

在将电子邮件地址绑定到您的 Worker 之前,您需要启用电子邮件路由并拥有至少一个经过验证的电子邮件地址。

配置对应域名的 `电子邮件 DNS 记录`

配置 `Cloudflare Email Routing` catch-all 发送到 `worker`

![email](vitepress-docs/docs/public/readme_assets/email.png)

---

## Cloudflare Pages 前端

第一次部署会提示创建项目, `production` 分支请填写 `production`

```bash
cd frontend
pnpm install
cp .env.example .env.local
```

修改 `.env.local` 文件, 将 `VITE_API_BASE` 修改为 `worker` 的 `url`, 不要在末尾加 `/`

例如: `VITE_API_BASE=https://xxx.xxx.workers.dev`

```bash
pnpm build --emptyOutDir
# 根据提示创建 pages
pnpm run deploy
```

![pages](vitepress-docs/docs/public/readme_assets/pages.png)

## 配置发送邮件

找到域名 `DNS` 记录的 `TXT` 的 `SPF` 记录, 增加 `include:relay.mailchannels.net`

```bash
v=spf1 include:_spf.mx.cloudflare.net include:relay.mailchannels.net ~all
```

新建 `_mailchannels` 记录, 类型为 `TXT`, 内容为 `v=mc1 cfid=你的worker域名`

- 此处 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/
- https://developers.cloudflare.com/pages/
- https://developers.cloudflare.com/workers/
- https://developers.cloudflare.com/email-routing/
2 changes: 2 additions & 0 deletions frontend/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const getOpenSettings = async (message) => {
}
}),
adminContact: res["adminContact"] || "",
enableUserDeleteEmail: res["enableUserDeleteEmail"] || false,
enableAutoReply: res["enableAutoReply"] || false,
};
if (openSettings.value.needAuth) {
showAuth.value = true;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const useGlobalState = createGlobalState(
prefix: '',
needAuth: false,
adminContact: '',
enableUserDeleteEmail: false,
enableAutoReply: false,
domains: [{
label: 'test.com',
value: 'test.com'
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/views/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const { t } = useI18n({
copy: 'Copy',
copied: 'Copied',
showPassword: 'Show Password',
fetchAddressError: 'Fetch address error, maybe your jwt is invalid or network error.',
fetchAddressError: 'Login password is invalid or account not exist, it may be network connection issue, please try again later.',
mailV1Alert: 'You have some mails in v1, please click here to login and visit your history mails.',
generateName: 'Generate Fake Name',
},
Expand Down Expand Up @@ -127,7 +127,7 @@ const { t } = useI18n({
copy: '复制',
copied: '已复制',
showPassword: '查看密码',
fetchAddressError: '获取地址失败, 请检查你的 jwt 是否有效 或 网络是否正常。',
fetchAddressError: '登录密码无效或账号不存在,也可能是网络连接异常,请稍后再尝试。',
mailV1Alert: '你有一些 v1 版本的邮件,请点击此处登录查看。',
generateName: '生成随机名字',
}
Expand Down Expand Up @@ -223,6 +223,7 @@ const menuOptions = computed(() => [
},
{ default: () => t('settings') }
),
show: openSettings.value.enableAutoReply,
key: "settings"
},
{
Expand All @@ -249,6 +250,7 @@ const menuOptions = computed(() => [
},
{ default: () => t('delteAccount') }
),
show: openSettings.value.enableUserDeleteEmail,
key: "delte_account"
}
]
Expand Down
26 changes: 18 additions & 8 deletions frontend/src/views/MailBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import { processItem, getDownloadEmlUrl } from '../utils/email-parser'
const message = useMessage()
const isMobile = useIsMobile()

const { settings, themeSwitch } = useGlobalState()
const { settings, openSettings, themeSwitch } = useGlobalState()
const autoRefresh = ref(false)
const autoRefreshInterval = ref(30)
const data = ref([])
const timer = ref(null)

Expand All @@ -29,6 +30,7 @@ const { t } = useI18n({
messages: {
en: {
autoRefresh: 'Auto Refresh',
refreshAfter: 'Refresh After {msg} Seconds',
refresh: 'Refresh',
attachments: 'Show Attachments',
downloadMail: 'Download Mail',
Expand All @@ -38,6 +40,7 @@ const { t } = useI18n({
},
zh: {
autoRefresh: '自动刷新',
refreshAfter: '{msg}秒后刷新',
refresh: '刷新',
downloadMail: '下载邮件',
attachments: '查看附件',
Expand All @@ -49,10 +52,16 @@ const { t } = useI18n({
});

const setupAutoRefresh = async (autoRefresh) => {
// auto refresh every 30 seconds
autoRefreshInterval.value = 30;
if (autoRefresh) {
timer.value = setInterval(async () => {
await refresh();
}, 30000)
autoRefreshInterval.value--;
if (autoRefreshInterval.value <= 0) {
autoRefreshInterval.value = 30;
await refresh();
}
}, 1000)
} else {
clearInterval(timer.value)
timer.value = null
Expand Down Expand Up @@ -125,19 +134,20 @@ onMounted(async () => {
<template>
<div>
<n-layout v-if="settings.address">
<n-split class="left" v-if="!isMobile" direction="horizontal" :max="0.75" :min="0.25" :default-size="0.25">
<n-split class="left" v-if="!isMobile" direction="horizontal" :max="0.75" :min="0.25" :default-size="0.3">
<template #1>
<div class="center">
<div style="display: inline-block; margin-top: 10px; margin-bottom: 10px;">
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
</div>
<n-switch v-model:value="autoRefresh" size="small">
<n-switch v-model:value="autoRefresh" size="small" :round="false">
<template #checked>
{{ t('autoRefresh') }}
{{ t('refreshAfter', { msg: autoRefreshInterval }) }}
</template>
<template #unchecked>
{{ t('autoRefresh') }}
</template></n-switch>
</template>
</n-switch>
<n-button @click="refresh" size="small" type="primary">
{{ t('refresh') }}
</n-button>
Expand Down Expand Up @@ -175,7 +185,7 @@ onMounted(async () => {
<n-tag type="info">
FROM: {{ curMail.source }}
</n-tag>
<n-popconfirm @positive-click="deleteMail">
<n-popconfirm v-if="openSettings.enableUserDeleteEmail" @positive-click="deleteMail">
<template #trigger>
<n-button tertiary type="error" size="small">{{ t('delete') }}</n-button>
</template>
Expand Down
4 changes: 4 additions & 0 deletions vitepress-docs/docs/en/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ PREFIX = "tmp" # The mailbox name prefix to be processed
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # your domain name
JWT_SECRET = "xxx" # Key used to generate jwt
BLACK_LIST = "" # Blacklist, used to filter senders, comma separated
# Allow users to delete messages
ENABLE_USER_DELETE_EMAIL = true
# Allow automatic replies to emails
ENABLE_AUTO_REPLY = false
# default send balance, if not set, it will be 0
# DEFAULT_SEND_BALANCE = 1
# dkim config
Expand Down
4 changes: 4 additions & 0 deletions vitepress-docs/docs/zh/guide/cli/worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ PREFIX = "tmp" # 要处理的邮箱名称前缀,不需要后缀可配置为空
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # 你的域名, 支持多个域名
JWT_SECRET = "xxx" # 用于生成 jwt 的密钥, jwt 用于给用户登录以及鉴权
BLACK_LIST = "" # 黑名单,用于过滤发件人,逗号分隔
# 允许用户删除邮件, 不配置则不允许
ENABLE_USER_DELETE_EMAIL = true
# 允许自动回复邮件
ENABLE_AUTO_REPLY = false
# 默认发送邮件余额,如果不设置,将为 0
# DEFAULT_SEND_BALANCE = 1
# dkim config
Expand Down
Loading