mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-07 08:27:08 +08:00
feat: add ENABLE_USER_DELETE_EMAIL && ENABLE_AUTO_REPLY && modify fetchAddressError i18n && UI: show autoRefreshInterval (#169)
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## [查看部署文档](https://temp-mail-docs.awsl.uk)
|
## [查看部署文档](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)
|
## [CHANGELOG](CHANGELOG.md)
|
||||||
|
|
||||||
@@ -21,20 +21,10 @@
|
|||||||
|
|
||||||
- [使用 cloudflare 免费服务,搭建临时邮箱](#使用-cloudflare-免费服务搭建临时邮箱)
|
- [使用 cloudflare 免费服务,搭建临时邮箱](#使用-cloudflare-免费服务搭建临时邮箱)
|
||||||
- [查看部署文档](#查看部署文档)
|
- [查看部署文档](#查看部署文档)
|
||||||
- [English](#english)
|
- [English Docs](#english-docs)
|
||||||
- [CHANGELOG](#changelog)
|
- [CHANGELOG](#changelog)
|
||||||
- [在线演示](#在线演示)
|
- [在线演示](#在线演示)
|
||||||
- [功能/TODO](#功能todo)
|
- [功能/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
|
## 功能/TODO
|
||||||
|
|
||||||
@@ -51,199 +41,3 @@
|
|||||||
- [x] 使用 rust wasm 解析邮件
|
- [x] 使用 rust wasm 解析邮件
|
||||||
- [x] 支持发送邮件
|
- [x] 支持发送邮件
|
||||||
- [x] 支持 DKIM
|
- [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 数据库
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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`
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cloudflare Email Routing
|
|
||||||
|
|
||||||
在将电子邮件地址绑定到您的 Worker 之前,您需要启用电子邮件路由并拥有至少一个经过验证的电子邮件地址。
|
|
||||||
|
|
||||||
配置对应域名的 `电子邮件 DNS 记录`
|
|
||||||
|
|
||||||
配置 `Cloudflare Email Routing` catch-all 发送到 `worker`
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 配置发送邮件
|
|
||||||
|
|
||||||
找到域名 `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/
|
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ const getOpenSettings = async (message) => {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
adminContact: res["adminContact"] || "",
|
adminContact: res["adminContact"] || "",
|
||||||
|
enableUserDeleteEmail: res["enableUserDeleteEmail"] || false,
|
||||||
|
enableAutoReply: res["enableAutoReply"] || false,
|
||||||
};
|
};
|
||||||
if (openSettings.value.needAuth) {
|
if (openSettings.value.needAuth) {
|
||||||
showAuth.value = true;
|
showAuth.value = true;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const useGlobalState = createGlobalState(
|
|||||||
prefix: '',
|
prefix: '',
|
||||||
needAuth: false,
|
needAuth: false,
|
||||||
adminContact: '',
|
adminContact: '',
|
||||||
|
enableUserDeleteEmail: false,
|
||||||
|
enableAutoReply: false,
|
||||||
domains: [{
|
domains: [{
|
||||||
label: 'test.com',
|
label: 'test.com',
|
||||||
value: 'test.com'
|
value: 'test.com'
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const { t } = useI18n({
|
|||||||
copy: 'Copy',
|
copy: 'Copy',
|
||||||
copied: 'Copied',
|
copied: 'Copied',
|
||||||
showPassword: 'Show Password',
|
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.',
|
mailV1Alert: 'You have some mails in v1, please click here to login and visit your history mails.',
|
||||||
generateName: 'Generate Fake Name',
|
generateName: 'Generate Fake Name',
|
||||||
},
|
},
|
||||||
@@ -127,7 +127,7 @@ const { t } = useI18n({
|
|||||||
copy: '复制',
|
copy: '复制',
|
||||||
copied: '已复制',
|
copied: '已复制',
|
||||||
showPassword: '查看密码',
|
showPassword: '查看密码',
|
||||||
fetchAddressError: '获取地址失败, 请检查你的 jwt 是否有效 或 网络是否正常。',
|
fetchAddressError: '登录密码无效或账号不存在,也可能是网络连接异常,请稍后再尝试。',
|
||||||
mailV1Alert: '你有一些 v1 版本的邮件,请点击此处登录查看。',
|
mailV1Alert: '你有一些 v1 版本的邮件,请点击此处登录查看。',
|
||||||
generateName: '生成随机名字',
|
generateName: '生成随机名字',
|
||||||
}
|
}
|
||||||
@@ -223,6 +223,7 @@ const menuOptions = computed(() => [
|
|||||||
},
|
},
|
||||||
{ default: () => t('settings') }
|
{ default: () => t('settings') }
|
||||||
),
|
),
|
||||||
|
show: openSettings.value.enableAutoReply,
|
||||||
key: "settings"
|
key: "settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -249,6 +250,7 @@ const menuOptions = computed(() => [
|
|||||||
},
|
},
|
||||||
{ default: () => t('delteAccount') }
|
{ default: () => t('delteAccount') }
|
||||||
),
|
),
|
||||||
|
show: openSettings.value.enableUserDeleteEmail,
|
||||||
key: "delte_account"
|
key: "delte_account"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import { processItem, getDownloadEmlUrl } from '../utils/email-parser'
|
|||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
const { settings, themeSwitch } = useGlobalState()
|
const { settings, openSettings, themeSwitch } = useGlobalState()
|
||||||
const autoRefresh = ref(false)
|
const autoRefresh = ref(false)
|
||||||
|
const autoRefreshInterval = ref(30)
|
||||||
const data = ref([])
|
const data = ref([])
|
||||||
const timer = ref(null)
|
const timer = ref(null)
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ const { t } = useI18n({
|
|||||||
messages: {
|
messages: {
|
||||||
en: {
|
en: {
|
||||||
autoRefresh: 'Auto Refresh',
|
autoRefresh: 'Auto Refresh',
|
||||||
|
refreshAfter: 'Refresh After {msg} Seconds',
|
||||||
refresh: 'Refresh',
|
refresh: 'Refresh',
|
||||||
attachments: 'Show Attachments',
|
attachments: 'Show Attachments',
|
||||||
downloadMail: 'Download Mail',
|
downloadMail: 'Download Mail',
|
||||||
@@ -38,6 +40,7 @@ const { t } = useI18n({
|
|||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
autoRefresh: '自动刷新',
|
autoRefresh: '自动刷新',
|
||||||
|
refreshAfter: '{msg}秒后刷新',
|
||||||
refresh: '刷新',
|
refresh: '刷新',
|
||||||
downloadMail: '下载邮件',
|
downloadMail: '下载邮件',
|
||||||
attachments: '查看附件',
|
attachments: '查看附件',
|
||||||
@@ -49,10 +52,16 @@ const { t } = useI18n({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const setupAutoRefresh = async (autoRefresh) => {
|
const setupAutoRefresh = async (autoRefresh) => {
|
||||||
|
// auto refresh every 30 seconds
|
||||||
|
autoRefreshInterval.value = 30;
|
||||||
if (autoRefresh) {
|
if (autoRefresh) {
|
||||||
timer.value = setInterval(async () => {
|
timer.value = setInterval(async () => {
|
||||||
await refresh();
|
autoRefreshInterval.value--;
|
||||||
}, 30000)
|
if (autoRefreshInterval.value <= 0) {
|
||||||
|
autoRefreshInterval.value = 30;
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
} else {
|
} else {
|
||||||
clearInterval(timer.value)
|
clearInterval(timer.value)
|
||||||
timer.value = null
|
timer.value = null
|
||||||
@@ -125,19 +134,20 @@ onMounted(async () => {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<n-layout v-if="settings.address">
|
<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>
|
<template #1>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<div style="display: inline-block; margin-top: 10px; margin-bottom: 10px;">
|
<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" />
|
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
|
||||||
</div>
|
</div>
|
||||||
<n-switch v-model:value="autoRefresh" size="small">
|
<n-switch v-model:value="autoRefresh" size="small" :round="false">
|
||||||
<template #checked>
|
<template #checked>
|
||||||
{{ t('autoRefresh') }}
|
{{ t('refreshAfter', { msg: autoRefreshInterval }) }}
|
||||||
</template>
|
</template>
|
||||||
<template #unchecked>
|
<template #unchecked>
|
||||||
{{ t('autoRefresh') }}
|
{{ t('autoRefresh') }}
|
||||||
</template></n-switch>
|
</template>
|
||||||
|
</n-switch>
|
||||||
<n-button @click="refresh" size="small" type="primary">
|
<n-button @click="refresh" size="small" type="primary">
|
||||||
{{ t('refresh') }}
|
{{ t('refresh') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
@@ -175,7 +185,7 @@ onMounted(async () => {
|
|||||||
<n-tag type="info">
|
<n-tag type="info">
|
||||||
FROM: {{ curMail.source }}
|
FROM: {{ curMail.source }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
<n-popconfirm @positive-click="deleteMail">
|
<n-popconfirm v-if="openSettings.enableUserDeleteEmail" @positive-click="deleteMail">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button tertiary type="error" size="small">{{ t('delete') }}</n-button>
|
<n-button tertiary type="error" size="small">{{ t('delete') }}</n-button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ PREFIX = "tmp" # The mailbox name prefix to be processed
|
|||||||
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # your domain name
|
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # your domain name
|
||||||
JWT_SECRET = "xxx" # Key used to generate jwt
|
JWT_SECRET = "xxx" # Key used to generate jwt
|
||||||
BLACK_LIST = "" # Blacklist, used to filter senders, comma separated
|
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, if not set, it will be 0
|
||||||
# DEFAULT_SEND_BALANCE = 1
|
# DEFAULT_SEND_BALANCE = 1
|
||||||
# dkim config
|
# dkim config
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ PREFIX = "tmp" # 要处理的邮箱名称前缀,不需要后缀可配置为空
|
|||||||
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # 你的域名, 支持多个域名
|
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # 你的域名, 支持多个域名
|
||||||
JWT_SECRET = "xxx" # 用于生成 jwt 的密钥, jwt 用于给用户登录以及鉴权
|
JWT_SECRET = "xxx" # 用于生成 jwt 的密钥, jwt 用于给用户登录以及鉴权
|
||||||
BLACK_LIST = "" # 黑名单,用于过滤发件人,逗号分隔
|
BLACK_LIST = "" # 黑名单,用于过滤发件人,逗号分隔
|
||||||
|
# 允许用户删除邮件, 不配置则不允许
|
||||||
|
ENABLE_USER_DELETE_EMAIL = true
|
||||||
|
# 允许自动回复邮件
|
||||||
|
ENABLE_AUTO_REPLY = false
|
||||||
# 默认发送邮件余额,如果不设置,将为 0
|
# 默认发送邮件余额,如果不设置,将为 0
|
||||||
# DEFAULT_SEND_BALANCE = 1
|
# DEFAULT_SEND_BALANCE = 1
|
||||||
# dkim config
|
# dkim config
|
||||||
|
|||||||
+27
-25
@@ -22,33 +22,35 @@ async function email(message, env, ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// auto reply email
|
// auto reply email
|
||||||
try {
|
if (env.ENABLE_AUTO_REPLY) {
|
||||||
const results = await env.DB.prepare(
|
try {
|
||||||
`SELECT * FROM auto_reply_mails where address = ? and enabled = 1`
|
const results = await env.DB.prepare(
|
||||||
).bind(message.to).first();
|
`SELECT * FROM auto_reply_mails where address = ? and enabled = 1`
|
||||||
if (results && results.source_prefix && message.from.startsWith(results.source_prefix)) {
|
).bind(message.to).first();
|
||||||
const msg = createMimeMessage();
|
if (results && results.source_prefix && message.from.startsWith(results.source_prefix)) {
|
||||||
msg.setHeader("In-Reply-To", message.headers.get("Message-ID"));
|
const msg = createMimeMessage();
|
||||||
msg.setSender({
|
msg.setHeader("In-Reply-To", message.headers.get("Message-ID"));
|
||||||
name: results.name || results.address,
|
msg.setSender({
|
||||||
addr: results.address
|
name: results.name || results.address,
|
||||||
});
|
addr: results.address
|
||||||
msg.setRecipient(message.from);
|
});
|
||||||
msg.setSubject(results.subject || "Auto-reply");
|
msg.setRecipient(message.from);
|
||||||
msg.addMessage({
|
msg.setSubject(results.subject || "Auto-reply");
|
||||||
contentType: 'text/plain',
|
msg.addMessage({
|
||||||
data: results.message || "This is an auto-reply message, please reconact later."
|
contentType: 'text/plain',
|
||||||
});
|
data: results.message || "This is an auto-reply message, please reconact later."
|
||||||
|
});
|
||||||
|
|
||||||
const replyMessage = new EmailMessage(
|
const replyMessage = new EmailMessage(
|
||||||
message.to,
|
message.to,
|
||||||
message.from,
|
message.from,
|
||||||
msg.asRaw()
|
msg.asRaw()
|
||||||
);
|
);
|
||||||
await message.reply(replyMessage);
|
await message.reply(replyMessage);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("reply email error", error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.log("reply email error", error);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.setReject(`Unknown address ${message.to}`);
|
message.setReject(`Unknown address ${message.to}`);
|
||||||
|
|||||||
+23
-10
@@ -34,6 +34,9 @@ api.get('/api/mails', async (c) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
api.delete('/api/mails/:id', async (c) => {
|
api.delete('/api/mails/:id', async (c) => {
|
||||||
|
if (c.env.ENABLE_USER_DELETE_EMAIL) {
|
||||||
|
return c.text("User delete email is disabled", 403)
|
||||||
|
}
|
||||||
const { address } = c.get("jwtPayload")
|
const { address } = c.get("jwtPayload")
|
||||||
const { id } = c.req.param();
|
const { id } = c.req.param();
|
||||||
const { success } = await c.env.DB.prepare(
|
const { success } = await c.env.DB.prepare(
|
||||||
@@ -82,16 +85,18 @@ api.get('/api/settings', async (c) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let auto_reply = {};
|
let auto_reply = {};
|
||||||
const results = await c.env.DB.prepare(
|
if (c.env.ENABLE_AUTO_REPLY) {
|
||||||
`SELECT * FROM auto_reply_mails where address = ? `
|
const results = await c.env.DB.prepare(
|
||||||
).bind(address).first();
|
`SELECT * FROM auto_reply_mails where address = ? `
|
||||||
if (results) {
|
).bind(address).first();
|
||||||
auto_reply = {
|
if (results) {
|
||||||
subject: results.subject,
|
auto_reply = {
|
||||||
message: results.message,
|
subject: results.subject,
|
||||||
enabled: results.enabled == 1,
|
message: results.message,
|
||||||
source_prefix: results.source_prefix,
|
enabled: results.enabled == 1,
|
||||||
name: results.name,
|
source_prefix: results.source_prefix,
|
||||||
|
name: results.name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { count: mailCountV1 } = await c.env.DB.prepare(
|
const { count: mailCountV1 } = await c.env.DB.prepare(
|
||||||
@@ -111,6 +116,9 @@ api.get('/api/settings', async (c) => {
|
|||||||
|
|
||||||
api.post('/api/settings', async (c) => {
|
api.post('/api/settings', async (c) => {
|
||||||
const { address } = c.get("jwtPayload")
|
const { address } = c.get("jwtPayload")
|
||||||
|
if (!c.env.ENABLE_AUTO_REPLY) {
|
||||||
|
return c.text("Auto reply is disabled", 403)
|
||||||
|
}
|
||||||
const { auto_reply } = await c.req.json();
|
const { auto_reply } = await c.req.json();
|
||||||
const { name, subject, source_prefix, message, enabled } = auto_reply;
|
const { name, subject, source_prefix, message, enabled } = auto_reply;
|
||||||
if ((!subject || !message) && enabled) {
|
if ((!subject || !message) && enabled) {
|
||||||
@@ -147,6 +155,8 @@ api.get('/open_api/settings', async (c) => {
|
|||||||
"domains": getDomains(c),
|
"domains": getDomains(c),
|
||||||
"needAuth": needAuth,
|
"needAuth": needAuth,
|
||||||
"adminContact": c.env.ADMIN_CONTACT,
|
"adminContact": c.env.ADMIN_CONTACT,
|
||||||
|
"enableUserDeleteEmail": c.env.ENABLE_USER_DELETE_EMAIL,
|
||||||
|
"enableAutoReply": c.env.ENABLE_AUTO_REPLY,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -204,6 +214,9 @@ api.get('/api/new_address', async (c) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
api.delete('/api/delete_address', async (c) => {
|
api.delete('/api/delete_address', async (c) => {
|
||||||
|
if (c.env.ENABLE_USER_DELETE_EMAIL) {
|
||||||
|
return c.text("User delete email is disabled", 403)
|
||||||
|
}
|
||||||
const { address } = c.get("jwtPayload")
|
const { address } = c.get("jwtPayload")
|
||||||
let name = address;
|
let name = address;
|
||||||
if (address.startsWith(c.env.PREFIX)) {
|
if (address.startsWith(c.env.PREFIX)) {
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ PREFIX = "tmp"
|
|||||||
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"]
|
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"]
|
||||||
JWT_SECRET = "xxx"
|
JWT_SECRET = "xxx"
|
||||||
BLACK_LIST = ""
|
BLACK_LIST = ""
|
||||||
# DEFAULT_SEND_BALANCE = 1 # default send balance, if not set, it will be 0
|
# 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
|
# dkim config
|
||||||
# DKIM_SELECTOR = ""
|
# DKIM_SELECTOR = ""
|
||||||
# DKIM_PRIVATE_KEY = ""
|
# DKIM_PRIVATE_KEY = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user