Compare commits

...

16 Commits

Author SHA1 Message Date
Dream Hunter
6ae3b0d85e feat: update docs (#574) 2025-01-24 17:36:59 +08:00
Dream Hunter
01e6cb1075 feat: |worker| health_check add JWT_SECRET and DOMAINS (#573) 2025-01-24 15:00:50 +08:00
Dream Hunter
814f6fada2 feat: |UI| admin worker config page add overflow: auto (#572) 2025-01-22 23:34:49 +08:00
Dream Hunter
31901aacc5 feat: update docs (#571) 2025-01-22 23:25:40 +08:00
Dream Hunter
fb9b9f6ae4 feat: update CHANGE LOG (#570) 2025-01-22 23:19:53 +08:00
Dream Hunter
095951ab45 feat: update docs (#569) 2025-01-22 23:14:38 +08:00
Dream Hunter
37614ce6fa feat: footer support html (#567) 2025-01-21 10:24:13 +08:00
Dream Hunter
3f81fbee6d feat: announcement support html (#566)
* feat: announcement support html

* feat: update dependencies
2025-01-20 13:53:40 +08:00
Dream Hunter
cf13236e7b fix: telegram mail page use iframe show email (#564) 2025-01-18 14:59:08 +08:00
Dream Hunter
36e9c611e6 feat: |Worker| add REMOVE_ALL_ATTACHMENT and REMOVE_EXCEED_SIZE_ATTAC… (#563)
feat: |Worker| add REMOVE_ALL_ATTACHMENT and REMOVE_EXCEED_SIZE_ATTACHMENT
2025-01-18 14:43:09 +08:00
Dream Hunter
047200c1c2 feat: |Worker| add REMOVE_ALL_ATTACHMENT and REMOVE_EXCEED_SIZE_ATTAC… (#562)
feat: |Worker| add REMOVE_ALL_ATTACHMENT and REMOVE_EXCEED_SIZE_ATTACHMENT
2025-01-18 14:12:01 +08:00
Dream Hunter
a22add0e14 fix: telegram mail page use iframe show email (#561) 2025-01-18 13:52:09 +08:00
Dream Hunter
7b1c4cc72a fix: mail-parser-wasm parsedEmailContext cache (#560) 2025-01-18 13:26:09 +08:00
刘志聪
3870727a08 fix: rpc headers covert & typo (#559)
Co-authored-by: liuzhicong <liuzhicong@dhgate.com>
2025-01-16 00:20:02 +08:00
Dream Hunter
2bb033964c feat: update doc (#557) 2025-01-11 18:56:36 +08:00
Dream Hunter
9db5a00b35 feat: v0.8.5 && update dependencies && fix deprecated warning for `… (#556)
feat: v0.8.5 && update dependencies && fix `deprecated` warning for `mail-parser-wasm-worker`
2025-01-11 18:46:46 +08:00
45 changed files with 2500 additions and 2414 deletions

View File

@@ -1,8 +1,8 @@
diff --git a/worker/src/common.ts b/worker/src/common.ts
index 8b63e8f..6a7c844 100644
index bd9bcc9..e7e2748 100644
--- a/worker/src/common.ts
+++ b/worker/src/common.ts
@@ -273,22 +273,22 @@ export const commonParseMail = async (parsedEmailContext: ParsedEmailContext): P
@@ -273,23 +273,23 @@ export const commonParseMail = async (parsedEmailContext: ParsedEmailContext): P
}
const raw_mail = parsedEmailContext.rawEmail;
// TODO: WASM parse email
@@ -10,9 +10,9 @@ index 8b63e8f..6a7c844 100644
- // const { parse_message_wrapper } = await import('mail-parser-wasm-worker');
+ try {
+ const { parse_message_wrapper } = await import('mail-parser-wasm-worker');
- // const parsedEmail = parse_message_wrapper(raw_mail);
- // return {
- // parsedEmailContext.parsedEmail = {
- // sender: parsedEmail.sender || "",
- // subject: parsedEmail.subject || "",
- // text: parsedEmail.text || "",
@@ -21,11 +21,12 @@ index 8b63e8f..6a7c844 100644
- // ) || [],
- // html: parsedEmail.body_html || "",
- // };
- // return parsedEmailContext.parsedEmail;
- // } catch (e) {
- // console.error("Failed use mail-parser-wasm-worker to parse email", e);
- // }
+ const parsedEmail = parse_message_wrapper(raw_mail);
+ return {
+ parsedEmailContext.parsedEmail = {
+ sender: parsedEmail.sender || "",
+ subject: parsedEmail.subject || "",
+ text: parsedEmail.text || "",
@@ -34,6 +35,7 @@ index 8b63e8f..6a7c844 100644
+ ) || [],
+ html: parsedEmail.body_html || "",
+ };
+ return parsedEmailContext.parsedEmail;
+ } catch (e) {
+ console.error("Failed use mail-parser-wasm-worker to parse email", e);
+ }

View File

@@ -1,14 +1,27 @@
<!-- markdownlint-disable-file MD004 MD024 MD034 MD036 -->
# CHANGE LOG
## main(v0.8.4)
## main(v0.8.6)
- feat: |UI| 公告支持 html 格式
- feat: |UI| `COPYRIGHT` 支持 html 格式
- feat: |Doc| 优化部署文档,补充了 `Github Actions 部署文档`,增加了 `Worker 变量说明`
## v0.8.5
- feat: |mail-parser-wasm-worker| 修复 `initSync` 函数调用时的 `deprecated` 参数警告
- feat: rpc headers covert & typo (#559)
- fix: telegram mail page use iframe show email (#561)
- feat: |Worker| 增加 `REMOVE_ALL_ATTACHMENT``REMOVE_EXCEED_SIZE_ATTACHMENT` 用于移除邮件附件,由于是解析邮件的一些信息会丢失,比如图片等.
## v0.8.4
- fix: |UI| 修复 admin portal 无收件人邮箱删除调用api 错误
- feat: |Telegram Bot| 增加 telegram bot 清理无效地址凭证命令
- feat: 增加 worker 配置 `DISABLE_ANONYMOUS_USER_CREATE_EMAIL` 禁用匿名用户创建邮箱地址,只允许登录用户创建邮箱地址
- feat: 增加 worker 配置 `ENABLE_ANOTHER_WORKER``ANOTHER_WORKER_LIST` ,用于调用其他 worker 的 rpc 接口 (#547)
- feat: |UI| 自动刷新配置保存到浏览器,可配置刷新间隔
- feat: 垃圾邮件检测增加存在才检查的列表 `JUNK_MAIL_CHECK_LIST` 配置
- feat: 垃圾邮件检测增加存在才检查的列表 `JUNK_MAIL_CHECK_LIST` 配置
- feat: | Worker | 增加 `ParsedEmailContext` 类用于缓存解析后的邮件内容,减少解析次数
- feat: |Github Action| Worker 部署增加 `DEBUG_MODE` 输出日志, `BACKEND_USE_MAIL_WASM_PARSER` 配置是否使用 wasm 解析邮件

View File

@@ -31,9 +31,9 @@
## [查看部署文档](https://temp-mail-docs.awsl.uk)
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/dreamhunter2333/cloudflare_temp_email)
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://temp-mail-docs.awsl.uk/zh/guide/actions/github-action.html)
[Github Action 部署文档](https://temp-mail-docs.awsl.uk/zh/guide/github-action.html)
[Github Action 部署文档](https://temp-mail-docs.awsl.uk/zh/guide/actions/github-action.html)
[English Docs](https://temp-mail-docs.awsl.uk/en/)

View File

@@ -1,6 +1,6 @@
{
"name": "cloudflare_temp_email",
"version": "0.8.4",
"version": "0.8.6",
"private": true,
"type": "module",
"scripts": {
@@ -19,16 +19,16 @@
"deploy:actions": "npm run build && wrangler pages deploy ./dist"
},
"dependencies": {
"@simplewebauthn/browser": "^10.0.0",
"@unhead/vue": "^1.11.14",
"@vueuse/core": "^12.2.0",
"@simplewebauthn/browser": "10.0.0",
"@unhead/vue": "^1.11.18",
"@vueuse/core": "^12.5.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.7.9",
"jszip": "^3.10.1",
"mail-parser-wasm": "^0.2.0",
"naive-ui": "^2.40.4",
"postal-mime": "^2.3.2",
"mail-parser-wasm": "^0.2.1",
"naive-ui": "^2.41.0",
"postal-mime": "^2.4.1",
"vooks": "^0.2.12",
"vue": "^3.5.13",
"vue-clipboard3": "^2.0.0",
@@ -39,14 +39,14 @@
"@vicons/fa": "^0.13.0",
"@vicons/material": "^0.13.0",
"@vitejs/plugin-vue": "^5.2.1",
"unplugin-auto-import": "^0.19.0",
"unplugin-vue-components": "^0.28.0",
"vite": "^6.0.6",
"unplugin-auto-import": "^19.0.0",
"unplugin-vue-components": "^28.0.0",
"vite": "^6.0.11",
"vite-plugin-pwa": "^0.21.1",
"vite-plugin-top-level-await": "^1.4.4",
"vite-plugin-wasm": "^3.4.1",
"workbox-build": "^7.3.0",
"workbox-window": "^7.3.0",
"wrangler": "^3.99.0"
"wrangler": "^3.104.0"
}
}

1208
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -61,24 +61,26 @@ onMounted(async () => {
<n-config-provider :locale="localeConfig" :theme="theme">
<n-global-style />
<n-spin description="loading..." :show="loading">
<n-message-provider container-style="margin-top: 20px;">
<n-grid x-gap="12" :cols="12">
<n-gi v-if="showSideMargin" span="1"></n-gi>
<n-gi :span="!showSideMargin ? 12 : 10">
<div class="main">
<n-space vertical>
<n-layout style="min-height: 80vh;">
<Header />
<router-view></router-view>
</n-layout>
<Footer />
</n-space>
</div>
</n-gi>
<n-gi v-if="showSideMargin" span="1"></n-gi>
</n-grid>
<n-back-top />
</n-message-provider>
<n-notification-provider container-style="margin-top: 60px;">
<n-message-provider container-style="margin-top: 20px;">
<n-grid x-gap="12" :cols="12">
<n-gi v-if="showSideMargin" span="1"></n-gi>
<n-gi :span="!showSideMargin ? 12 : 10">
<div class="main">
<n-space vertical>
<n-layout style="min-height: 80vh;">
<Header />
<router-view></router-view>
</n-layout>
<Footer />
</n-space>
</div>
</n-gi>
<n-gi v-if="showSideMargin" span="1"></n-gi>
</n-grid>
<n-back-top />
</n-message-provider>
</n-notification-provider>
</n-spin>
</n-config-provider>
</template>

View File

@@ -1,4 +1,5 @@
import { useGlobalState } from '../store'
import { h } from 'vue'
import axios from 'axios'
const API_BASE = import.meta.env.VITE_API_BASE || "";
@@ -52,7 +53,7 @@ const apiFetch = async (path, options = {}) => {
}
}
const getOpenSettings = async (message) => {
const getOpenSettings = async (message, notification) => {
try {
const res = await api.fetch("/open_api/settings");
const domainLabels = res["domainLabels"] || [];
@@ -89,10 +90,12 @@ const getOpenSettings = async (message) => {
}
if (openSettings.value.announcement && openSettings.value.announcement != announcement.value) {
announcement.value = openSettings.value.announcement;
message.info(announcement.value, {
showIcon: false,
duration: 0,
closable: true
notification.info({
content: () => {
return h("div", {
innerHTML: announcement.value
});
}
});
}
} catch (error) {

View File

@@ -21,9 +21,14 @@ const { t } = useI18n({
<div>
<n-divider class="footer-divider" />
<div style="text-align: center; padding: 20px">
<n-text depth="3">
{{ t('copyright') }} © 2023-{{ new Date().getFullYear() }} {{ openSettings.copyright }}
</n-text>
<n-space justify="center">
<n-text depth="3">
{{ t('copyright') }} © 2023-{{ new Date().getFullYear() }}
</n-text>
<n-text depth="3">
<div v-html="openSettings.copyright"></div>
</n-text>
</n-space>
</div>
</div>
</template>

View File

@@ -15,6 +15,7 @@ import { api } from '../api'
import { getRouterPathWithLang } from '../utils'
const message = useMessage()
const notification = useNotification()
const {
toggleDark, isDark, isTelegram, showAdminPage,
@@ -223,7 +224,7 @@ const logoClick = async () => {
}
onMounted(async () => {
await api.getOpenSettings(message);
await api.getOpenSettings(message, notification);
// make sure user_id is fetched
if (!userSettings.value.user_id) await api.getUserSettings(message);
});

View File

@@ -26,7 +26,7 @@ onMounted(async () => {
<template>
<div class="center">
<n-card :bordered="false" embedded style="max-width: 600px;">
<n-card :bordered="false" embedded style="max-width: 600px; overflow: auto;">
<pre>{{ JSON.stringify(settings, null, 2) }}</pre>
</n-card>
</div>

View File

@@ -1,10 +1,13 @@
<script setup>
import { GithubAlt, Discord, Telegram } from '@vicons/fa'
import { useGlobalState } from '../../store'
const { announcement } = useGlobalState()
</script>
<template>
<div class="center">
<n-card :bordered="false" embedded>
<div v-html="announcement"></div>
<n-button tag="a" target="_blank" href="https://github.com/dreamhunter2333/cloudflare_temp_email">
<template #icon>
<n-icon :component="GithubAlt" />

View File

@@ -34,6 +34,7 @@ const props = defineProps({
})
const message = useMessage()
const notification = useNotification()
const router = useRouter()
const {
@@ -195,7 +196,7 @@ const showNewAddressTab = computed(() => {
onMounted(async () => {
if (!openSettings.value.domains || openSettings.value.domains.length === 0) {
await api.getOpenSettings();
await api.getOpenSettings(message, notification);
}
emailDomain.value = domainsOptions.value ? domainsOptions.value[0]?.value : "";
});

View File

@@ -46,7 +46,7 @@ onMounted(async () => {
<template>
<div class="center">
<n-card :bordered="false" embedded v-if="curMail.message" style="max-width: 800px; overflow: auto;">
<n-card :bordered="false" embedded v-if="curMail.message" style="max-width: 800px; height: 100%;">
<n-tag type="info">
ID: {{ curMail.id }}
</n-tag>
@@ -59,7 +59,8 @@ onMounted(async () => {
<n-tag v-if="showEMailTo" type="info">
TO: {{ curMail.address }}
</n-tag>
<div v-html="curMail.message" style="margin-top: 10px;"></div>
<iframe :srcdoc="curMail.message" style="margin-top: 10px;width: 100%; height: 100%;">
</iframe>
</n-card>
</div>
</template>
@@ -71,5 +72,6 @@ onMounted(async () => {
text-align: left;
place-items: center;
justify-content: center;
height: 80vh;
}
</style>

View File

@@ -24,6 +24,7 @@ export default defineConfig({
{
'naive-ui': [
'useMessage',
'useNotification',
'NButton',
'NPopconfirm',
'NIcon',

View File

@@ -1,6 +1,6 @@
[package]
name = "mail-parser-wasm"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
description = "A simple mail parser for wasm"
license = "MIT"

View File

@@ -1,12 +1,12 @@
import initAsync, { initSync, parse_message } from './mail_parser_wasm';
import MODULE from './mail_parser_wasm_bg.wasm';
initSync(MODULE);
initSync({ module: MODULE });
export { initAsync, MODULE };
export * from './mail_parser_wasm';
export const parse_message_wrapper = (raw_message) => {
initSync(MODULE);
initSync({ module: MODULE });
return parse_message(raw_message);
}

View File

@@ -7,7 +7,7 @@
"url": "https://github.com/dreamhunter2333/cloudflare_temp_email",
"directory": "mail-parser-wasm"
},
"version": "0.2.0",
"version": "0.2.1",
"license": "MIT",
"files": [
"mail_parser_wasm_bg.wasm",

View File

@@ -1,6 +1,6 @@
{
"name": "temp-email-pages",
"version": "0.8.4",
"version": "0.8.6",
"description": "",
"main": "index.js",
"scripts": {
@@ -11,6 +11,6 @@
"author": "",
"license": "ISC",
"devDependencies": {
"wrangler": "^3.99.0"
"wrangler": "^3.104.0"
}
}

15
scripts/update-dependencies.sh Executable file
View File

@@ -0,0 +1,15 @@
cd frontend/
pnpm up
cd ..
cd worker/
pnpm up
cd ..
cd pages/
pnpm up
cd ..
cd vitepress-docs/
pnpm up
cd ..

View File

@@ -119,9 +119,21 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] {
},
{
text: '通过 Github Actions 部署',
collapsed: true,
items: [
{ text: 'D1 数据库', link: 'actions/d1' },
{ text: 'Github Actions 配置', link: 'actions/github-action' },
{ text: '配置邮件转发', link: 'email-routing.md' },
{ text: '配置发送邮件', link: 'config-send-mail' },
{ text: '自动更新配置', link: 'actions/auto-update' },
]
},
{
text: '通用',
collapsed: false,
items: [
{ text: '通过 Github Actions 部署', link: 'github-action' },
{ text: 'worker变量说明', link: 'worker-vars' },
{ text: '常见问题', link: 'common-issues' },
]
},
{

View File

@@ -77,13 +77,13 @@ compatibility_flags = [ "nodejs_compat" ]
# TITLE = "Custom Title" # The title of the site
PREFIX = "tmp" # The mailbox name prefix to be processed
# (min, max) length of the adderss, if not set, the default is (1, 30)
# MIN_ADDRESS_LEN = 1
# MAX_ADDRESS_LEN = 30
# ANNOUNCEMENT = "Custom Announcement"
# address check REGEX, if not set, will not check
# ADDRESS_CHECK_REGEX = "^(?!.*admin).*"
# address name replace REGEX, if not set, the default is [^a-z0-9]
# ADDRESS_REGEX = "[^a-z0-9]"
# MIN_ADDRESS_LEN = 1
# MAX_ADDRESS_LEN = 30
# If you want your site to be private, uncomment below and change your password
# PASSWORDS = ["123", "456"]
# admin console password, if not configured, access to the console is not allowed
@@ -138,6 +138,10 @@ ENABLE_AUTO_REPLY = false
# JUNK_MAIL_CHECK_LIST = = ["spf", "dkim", "dmarc"]
# junk mail force check pass list, if no status or status is not pass, will be marked as junk mail
# JUNK_MAIL_FORCE_PASS_LIST = ["spf", "dkim", "dmarc"]
# remove attachment if size exceed 2MB, mail maybe mising some information due to parsing
# REMOVE_EXCEED_SIZE_ATTACHMENT = true
# remove all attachment, mail maybe mising some information due to parsing
# REMOVE_ALL_ATTACHMENT = true
[[d1_databases]]
binding = "DB"

View File

@@ -15,14 +15,17 @@ hero:
- theme: alt
text: 通过用户界面部署
link: /zh/guide/quick-start
- theme: alt
text: 通过 Github Actions 部署
link: /zh/guide/quick-start
features:
- title: 免费托管在 CloudFlare无需服务器
details: Cloudflare D1 数据库Cloudflare Pages 前端Cloudflare Workers 后端, Cloudflare Email Routing
- title: 仅需域名即可私有部署
details: 支持 password 登录邮箱,使用访问密码可作为私人站点,支持附件功能
- title: 仅需域名即可私有部署, 免费托管在 CloudFlare无需服务器
details: 支持 password 登录邮箱, 用户注册,使用访问密码可作为私人站点,支持附件功能。
- title: 使用 rust wasm 解析邮件
details: 使用 rust wasm 解析邮件支持邮件各种RFC标准支持附件, 速度极快
- title: 支持 Telegram Bot 和 Webhook
details: 邮件可转发到 Telegram 或者 webhook, Telegram Bot 支持绑定邮箱,查看邮件, Telegram 小程序
- title: 支持发送邮件(UI/API/SMTP)
details: 支持通过域名邮箱发送 txt 或者 html 邮件,支持 DKIM 签名, UI/API/SMTP 发送邮件
---

View File

@@ -0,0 +1,10 @@
# Github Actions 部署如何配置自动更新
::: warning 注意
有问题请通过 `Github Issues` 反馈,感谢。
自动更新不会执行 D1 数据库的 sql 文件,当数据库 schema 变动时,需要手动执行。
:::
1. 打开仓库的 `Actions` 页面,找到 `Upstream Sync`,点击 `enable workflow` 启用 `workflow`
2. 如果 `Upstream Sync` 运行失败,到仓库主页点击 `Sync` 手动同步即可
3. 修改 `Upstream Sync``schedule` 配置可自定义更新间隔,参考 [cron 表达式](https://crontab.guru/)

View File

@@ -0,0 +1,3 @@
# 初始化/更新 D1 数据库
参考 [命令行更新 d1](/zh/guide/cli/d1) 或者 [用户界面更新 d1](/zh/guide/ui/d1)

View File

@@ -1,16 +1,13 @@
# 通过 Github Actions 部署
::: warning 注意
目前只支持 worker 和 pages 的部署D1 数据库以及 Email 部分请参考 [UI/CLI 部署](/)
目前只支持 worker 和 pages 的部署。
有问题请通过 `Github Issues` 反馈,感谢。
自动更新不会执行 sql 文件,需要手动执行。
:::
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/dreamhunter2333/cloudflare_temp_email)
## 部署步骤
1. 点击按钮 fork 本仓库 或者直接 fork 本仓库
1. 在 GitHub fork 本仓库
2. 打开仓库的 `Actions` 页面,找到 `Deploy Backend Production``Deploy Frontend`,点击 `enable workflow` 启用 `workflow`

View File

@@ -22,6 +22,9 @@ wrangler kv:namespace create DEV
## 修改 `wrangler.toml` 配置文件
> [!NOTE] 注意
> 更多变量的配置请查看 [worker变量说明](/zh/guide/worker-vars)
```toml
name = "cloudflare_temp_email"
main = "src/worker.ts"
@@ -43,90 +46,20 @@ compatibility_flags = [ "nodejs_compat" ]
# ]
[vars]
# TITLE = "Custom Title" # 自定义网站标题
PREFIX = "tmp" # 要处理的邮箱名称前缀,不需要后缀可配置为空字符串
# (min, max) adderss的长度如果不设置默认为(1, 30)
# ANNOUNCEMENT = "Custom Announcement" # 自定义公告
# address name 的正则表达式, 只用于检查,符合条件将通过检查
# ADDRESS_CHECK_REGEX = "^(?!.*admin).*"
# address name 替换非法符号的正则表达式, 不在其中的符号将被替换,如果不设置,默认为 [^a-z0-9], 需谨慎使用, 有些符号可能导致无法收件
# ADDRESS_REGEX = "[^a-z0-9]"
# MIN_ADDRESS_LEN = 1
# MAX_ADDRESS_LEN = 30
# 如果你想要你的网站私有,取消下面的注释,并修改密码
# PASSWORDS = ["123", "456"]
# 邮箱名称前缀,不需要后缀可配置为空字符串或者不配置
PREFIX = "tmp"
# 用于临时邮箱的所有域名, 支持多个域名
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"]
# 用于生成 jwt 的密钥, jwt 用于给用户登录以及鉴权
JWT_SECRET = "xxx"
# admin 控制台密码, 不配置则不允许访问控制台
# ADMIN_PASSWORDS = ["123", "456"]
# 警告: 管理员控制台没有密码或用户检查
# DISABLE_ADMIN_PASSWORD_CHECK = false
# admin 联系方式,不配置则不显示,可配置任意字符串
# ADMIN_CONTACT = "xx@xx.xxx"
# DEFAULT_DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # 默认用户可用的域名(未登录或未分配角色的用户)
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # 你的域名, 支持多个域名
# 对于中文域名,可以使用 DOMAIN_LABELS 显示域名的中文展示名称
# DOMAIN_LABELS = ["中文.xxx", "xxx.xxx2"]
# 新用户默认角色, 仅在启用邮件验证时有效
# USER_DEFAULT_ROLE = "vip"
# admin 角色配置, 如果用户角色等于 ADMIN_USER_ROLE 则可以访问 admin 控制台
# ADMIN_USER_ROLE = "admin" # the role which can access admin panel
# 用户角色配置, 如果 domains 为空将使用 default_domains
# 如果 prefix 为 null 将使用默认前缀, 如果 prefix 为空字符串将不使用前缀
# USER_ROLES = [
# { domains = ["xxx.xxx1" , "xxx.xxx2"], role = "vip", prefix = "vip" },
# { domains = ["xxx.xxx1" , "xxx.xxx2"], role = "admin", prefix = "" },
# ]
JWT_SECRET = "xxx" # 用于生成 jwt 的密钥, jwt 用于给用户登录以及鉴权
BLACK_LIST = "" # 黑名单,用于过滤发件人,逗号分隔
# 是否允许用户创建邮件, 不配置则不允许
ENABLE_USER_CREATE_EMAIL = true
# 禁用匿名用户创建邮箱,如果设置为 true则用户只能在登录后创建邮箱地址
# DISABLE_ANONYMOUS_USER_CREATE_EMAIL = true
# 允许用户删除邮件, 不配置则不允许
ENABLE_USER_DELETE_EMAIL = true
# 允许自动回复邮件
ENABLE_AUTO_REPLY = false
# 是否启用 webhook
# ENABLE_WEBHOOK = true
# 前端界面页脚文本
# COPYRIGHT = "Dream Hunter"
# DISABLE_SHOW_GITHUB = true # 是否显示 GitHub 链接
# 默认发送邮件余额,如果不设置,将为 0
# DEFAULT_SEND_BALANCE = 1
# NO_LIMIT_SEND_ROLE = "vip" # 可以无限发送邮件的角色
# Turnstile 人机验证配置
# CF_TURNSTILE_SITE_KEY = ""
# CF_TURNSTILE_SECRET_KEY = ""
# telegram bot 最多绑定邮箱数量
# TG_MAX_ADDRESS = 5
# telegram BOT_INFO预定义的 BOT_INFO 可以降低 webhook 的延迟
# TG_BOT_INFO = "{}"
# 全局转发地址列表,如果不配置则不启用,启用后所有邮件都会转发到列表中的地址
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
# 前端地址,用于发送 webhook 的邮件 url
# FRONTEND_URL = "https://xxxx.xxx"
# 是否启用垃圾邮件检查,默认任何一项存在配置且不通过则被判定为垃圾邮件
# ENABLE_CHECK_JUNK_MAIL = false
# 垃圾邮件检查配置, 任何一项 存在 且 不通过 则被判定为垃圾邮件
# JUNK_MAIL_CHECK_LIST = = ["spf", "dkim", "dmarc"]
# 垃圾邮件检查配置, 任何一项 不存在 或者 不通过 则被判定为垃圾邮件
# JUNK_MAIL_FORCE_PASS_LIST = ["spf", "dkim", "dmarc"]
# 是否开启其他 worker 处理邮件
# ENABLE_ANOTHER_WORKER = false
# 其他 worker 处理邮件的配置,可以配置多个其他 worker。
# 通过关键词筛选,调用对应绑定的 worker 的方法(默认方法名为 rpcEmail
# keywords必填否则 worker 将不会被触发
#ANOTHER_WORKER_LIST ="""
#[
# {
# "binding":"AUTH_INBOX",
# "method":"rpcEmail",
# "keywords":[
# "验证码","激活码","激活链接","确认链接","验证邮箱","确认邮件","账号激活","邮件验证","账户确认","安全码","认证码","安全验证","登陆码","确认码","启用账户","激活账户","账号验证","注册确认",
# "account","activation","verify","verification","activate","confirmation","email","code","validate","registration","login","code","expire","confirm"
# ]
# }
#]
#"""
# D1 数据库的名称和 ID 可以在 cloudflare 控制台查看
[[d1_databases]]

View File

@@ -0,0 +1,41 @@
# 常见问题
> [!NOTE] 注意
> 如果你的问题没有在这里找到解决方案,请到 `Github Issues` 中搜索或者提问, 或者到 Telegram 群组中提问。
## 通用
| 问题 | 解决方案 |
| ---------------------------------------------- | ------------------------------------------------------------------------------- |
| 使用 Cloudflare Workers 给已认证的邮箱发送邮件 | 使用 cf 的 API 进行发送,只支持绑定到 CF 上的收件地址,即 CF EMAIL 转发目的地址 |
| 绑定多个域名 | 每个域名都需要设置 email 转发到 worker |
## worker 相关
| 问题 | 解决方案 |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| `Uncaught Error: No such module "path". imported from "worker.js"` | [参考](/zh/guide/ui/worker) |
| `No such module "node:stream". imported from "worker.js"` | [参考](/zh/guide/ui/worker) |
| `二级域名无法发送邮件` | [参考](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/515) |
| `Failed to send verify code: No balance` | admin 后台设置无限制邮件或者发件权限页面增加额度 |
| `Github OAuth无法获取到邮箱 400 Failed to get user email` | 需要 github 用户设置公开邮箱 |
| `Cannot read properties of undefined (reading 'map')` | worker 变量没有设置成功 |
## pages 相关
| 问题 | 解决方案 |
| --------------- | ---------------------------------------- |
| `network error` | 使用无痕模式或者清空浏览器缓存DNS 缓存 |
## telegram bot
| 问题 | 解决方案 |
| -------------------------------------------------------------- | -------------------------------------------------- |
| `Telgram Bot获取邮件失败400Bad Request:BUTTON_URL_INVALID` | tg mini app 的 URL 填写错误,需要填写 pages 的 URL |
| `Telegram bot bind error: bind adress count reach the limit` | 需要设置 worker 变量 `TG_MAX_ADDRESS` |
## Github Actions
| 问题 | 解决方案 |
| ------------------------------------------ | --------------------------------------------------------------------------------- |
| Github Action部署后cf里始终是preview分支 | 到 cf pages 页面的设置中确认 前端的分支 和 Github Action 的 前端部署分支 是否相同 |

View File

@@ -1,5 +1,7 @@
# 配置 Telegram Bot
试用地址:[@cf_temp_mail_bot](https://t.me/cf_temp_mail_bot)
::: warning 注意
worker 默认的 `worker.dev` 域名的证书是不被 telegram 支持的,配置 Telegram Bot 请使用自定义域名
:::

View File

@@ -1,13 +1,16 @@
# 快速开始
- 良好的网络环境
- cloudflare 账号
## 开始之前
打开 [cloudflare控制台](https://dash.cloudflare.com/)
需要 `良好的网络环境``cloudflare 账号` 打开 [cloudflare控制台](https://dash.cloudflare.com/)
查看通过 [命令行部署](/zh/guide/cli/pre-requisite) 或者 [用户界面部署](/zh/guide/ui/d1)
选择下面三种方式之一进行部署
## 网友提供的详细的小白教程
- [通过命令行部署](/zh/guide/cli/pre-requisite)
- [通过用户界面部署](/zh/guide/ui/d1)
- [通过Github Actions 部署](/zh/guide/actions/github-action)
### 也可以参考网友提供的详细的小白教程
- [【教程】小白也能看懂的自建Cloudflare临时邮箱教程域名邮箱](https://linux.do/t/topic/316819/1)
@@ -22,14 +25,18 @@
然后参考下面的文档使用 `CLI` 或者 `UI` 覆盖部署之前的 `worker``pages` 即可
CLI 部署
### CLI 部署
- [命令行更新 d1](/zh/guide/cli/d1)
- [命令行部署 worker](/zh/guide/cli/worker)
- [命令行部署 pages](/zh/guide/cli/worker)
UI 部署
### UI 部署
- [用户界面更新 d1](/zh/guide/ui/d1)
- [用户界面部署 worker](/zh/guide/ui/worker)
- [用户界面部署 pages](/zh/guide/ui/pages)
### Github Actions 部署
- [Github Actions 部署如何配置自动更新](/zh/guide/actions/auto-update)

View File

@@ -26,34 +26,36 @@
![worker2](/ui_install/worker-2.png)
![worker-upload](/ui_install/worker-upload.png)
6. 点击 `Settings` -> `Variables`, 如图所示添加变量,参考 [修改 wrangler.toml 配置文件](/zh/guide/cli/worker.html#修改-wrangler-toml-配置文件) 中的 `[vars]` 部分
> [!NOTE]
> 注意字符串格式的变量的最外层的引号是不需要的
>
> - 对于 `USER_ROLES` 请配置为此格式 `[{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"vip","prefix":"vip"},{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"admin","prefix":""}]`
6. 点击 `Settings` -> `Variables`, 如图所示添加变量
![worker-var](/ui_install/worker-var.png)
7. 以下是 `Settings` -> `Variables` 中必须配置的变量列表
> [!NOTE] 注意
> 更多变量的配置请查看 [worker变量说明](/zh/guide/worker-vars)
>
> 注意字符串格式的变量的最外层的引号是不需要的
>
> 对于 `USER_ROLES` 请配置为此格式 `[{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"vip","prefix":"vip"},{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"admin","prefix":""}]`
| 变量名 | 说明 | 示例 |
| -------------------------- | ------------------------------------------------ | ------------------------------------ |
| `PREFIX` | 要处理的邮箱名称前缀,不需要后缀可配置为空字符串 | `tmp` |
| `DOMAINS` | 你的域名, 支持多个域名 | `["awsl.uk", "dreamhunter2333.xyz"]` |
| `ADMIN_PASSWORDS` | admin 控制台密码, 不配置则不允许访问控制台 | `["123", "456"]` |
| `JWT_SECRET` | 用于生成 jwt 的密钥, jwt 用于登录以及鉴权 | `xxx` |
| `ENABLE_USER_CREATE_EMAIL` | 是否允许用户创建邮箱, 不配置则不允许 | `true` |
| `ENABLE_USER_DELETE_EMAIL` | 是否允许用户删除邮箱, 不配置则不允许 | `true` |
建议配置的变量列表
8. 点击 `Settings` -> `Variables`, 下拉找到 `D1 Database`, 点击 `Add Binding`, 名称如图,选择刚刚创建的 D1 数据库,点击 `Deploy`
| 变量名 | 类型 | 说明 | 示例 |
| -------------------------- | ----------- | ------------------------------------------ | ------------------------------------ |
| `PREFIX` | 文本 | 新建邮箱名称默认前缀,不需要前缀可不配置 | `tmp` |
| `DOMAINS` | JSON | 用于临时邮箱的所有域名, 支持多个域名 | `["awsl.uk", "dreamhunter2333.xyz"]` |
| `JWT_SECRET` | 文本/Secret | 用于生成 jwt 的密钥, jwt 用于登录以及鉴权 | `xxx` |
| `ADMIN_PASSWORDS` | JSON | admin 控制台密码, 不配置则不允许访问控制台 | `["123", "456"]` |
| `ENABLE_USER_CREATE_EMAIL` | 文本/JSON | 是否允许用户创建邮箱, 不配置则不允许 | `true` |
| `ENABLE_USER_DELETE_EMAIL` | 文本/JSON | 是否允许用户删除邮件, 不配置则不允许 | `true` |
7. 点击 `Settings` -> `Variables`, 下拉找到 `D1 Database`, 点击 `Add Binding`, 名称如图,选择刚刚创建的 D1 数据库,点击 `Deploy`
> [!NOTE] 重要
> 注意此处 `D1 Database` 的绑定名称必须为 `DB`
![worker-d1](/ui_install/worker-d1.png)
9. 点击 `Settings` -> `Trggers`, 这里可以添加自己的域名,你也可以使用自动生成的 `*.workers.dev` 的域名。记录下这个域名,后面部署前端会用到。
8. 点击 `Settings` -> `Trggers`, 这里可以添加自己的域名,你也可以使用自动生成的 `*.workers.dev` 的域名。记录下这个域名,后面部署前端会用到。
> [!NOTE]
> 打开 `worker` 的 `url`,如果显示 `OK` 说明部署成功
@@ -62,7 +64,7 @@
![worker3](/ui_install/worker-3.png)
10. 如果你要启用注册用户功能,并需要发送邮件验证,则需要创建 `KV` 缓存, 不需要可跳过此步骤,点击 `Workers & Pages` -> `KV` -> `Create Namespace`, 如图,点击 `Create Namespace`,然后在 `Settings` -> `Variables`, 下拉找到 `KV`, 点击 `Add Binding`, 名称如图,选择刚刚创建的 `KV` 缓存,点击 `Deploy`
9. 如果你要启用注册用户功能,并需要发送邮件验证,则需要创建 `KV` 缓存, 不需要可跳过此步骤,点击 `Workers & Pages` -> `KV` -> `Create Namespace`, 如图,点击 `Create Namespace`,然后在 `Settings` -> `Variables`, 下拉找到 `KV`, 点击 `Add Binding`, 名称如图,选择刚刚创建的 `KV` 缓存,点击 `Deploy`
> [!NOTE] 重要
> 如果你要启用注册用户功能,并需要发送邮件验证,则需要创建 `KV` 缓存, 不需要可跳过此步骤
@@ -72,9 +74,14 @@
![worker-kv](/ui_install/worker-kv.png)
![worker-kv-bind](/ui_install/worker-kv-bind.png)
11. Telegram Bot 配置
10. Telegram Bot 配置
> [!NOTE]
> 如果不需要 Telegram Bot, 可跳过此步骤
请先创建一个 Telegram Bot然后获取 `token`,然后执行下面的命令,将 `token` 添加到 `Variables` 中, Name: `TELEGRAM_BOT_TOKEN`
11. 如果你想要使用 admin 页面中的定时任务清理邮件,需要到 `Settings` -> `Triggers` -> `Cron Triggers` 中添加定时任务.
> [!NOTE]
> 选择 `cron` 表达式,输入 `0 0 * * *`(此表达式表示每天午夜运行),点击 `Add` 增加。请根据您的需求调整此表达式。

View File

@@ -0,0 +1,138 @@
# Worker 变量说明
> [!NOTE] 注意
> 通过 CLI 部署时的写法请参考 `worker/wrangler.toml.template`
## 必填变量
| 变量名 | 类型 | 说明 | 示例 |
| -------------------------- | ----------- | ------------------------------------------ | ------------------------------------ |
| `DOMAINS` | JSON | 用于临时邮箱的所有域名, 支持多个域名 | `["awsl.uk", "dreamhunter2333.xyz"]` |
| `JWT_SECRET` | 文本/Secret | 用于生成 jwt 的密钥, jwt 用于登录以及鉴权 | `xxx` |
| `ADMIN_PASSWORDS` | JSON | admin 控制台密码, 不配置则不允许访问控制台 | `["123", "456"]` |
| `ENABLE_USER_CREATE_EMAIL` | 文本/JSON | 是否允许用户创建邮箱, 不配置则不允许 | `true` |
| `ENABLE_USER_DELETE_EMAIL` | 文本/JSON | 是否允许用户删除邮件, 不配置则不允许 | `true` |
## 后台相关变量
| 变量名 | 类型 | 说明 | 示例 |
| ------------------------------ | --------- | ------------------------------------ | ---------------- |
| `PASSWORDS` | JSON | 网站私有密码, 配置后需要密码才能访问 | `["123", "456"]` |
| `DISABLE_ADMIN_PASSWORD_CHECK` | 文本/JSON | 警告: 管理员控制台没有密码或用户检查 | `false` |
## 邮箱相关变量
| 变量名 | 类型 | 说明 | 示例 |
| ---------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `PREFIX` | 文本 | 新建 `邮箱名称` 的默认前缀,不需要前缀可不配置 | `tmp` |
| `MIN_ADDRESS_LEN` | 数字 | `邮箱名称` 的最小长度 | `1` |
| `MAX_ADDRESS_LEN` | 数字 | `邮箱名称` 的最大长度 | `30` |
| `ADDRESS_CHECK_REGEX` | 文本 | `邮箱名称` 的正则表达式, 只用于检查 | `^(?!.*admin).*` |
| `ADDRESS_REGEX` | 文本 | `邮箱名称` 替换非法符号的正则表达式, 不在其中的符号将被替换,如果不设置,默认为 `[^a-z0-9]`, 需谨慎使用, 有些符号可能导致无法收件 | `[^a-z0-9]` |
| `DEFAULT_DOMAINS` | JSON | 默认用户可用的域名(未登录或未分配角色的用户) | `["awsl.uk", "dreamhunter2333.xyz"]` |
| `DOMAIN_LABELS` | JSON | 对于中文域名,可以使用 DOMAIN_LABELS 显示域名的中文展示名称 | `["中文.awsl.uk", "dreamhunter2333.xyz"]` |
| `ENABLE_AUTO_REPLY` | 文本/JSON | 允许自动回复邮件 | `true` |
| `DEFAULT_SEND_BALANCE` | 文本/JSON | 默认发送邮件余额,如果不设置,将为 0 | `1` |
## 接受邮件相关变量
| 变量名 | 类型 | 说明 | 示例 |
| ------------------------------- | --------- | -------------------------------------------------------------------------- | -------------------------- |
| `BLACK_LIST` | 文本 | 黑名单,用于过滤发件人,逗号分隔 | `gov.cn,edu.cn` |
| `ENABLE_CHECK_JUNK_MAIL` | 文本/JSON | 是否启用垃圾邮件检查,配合下列两个列表使用 | `false` |
| `JUNK_MAIL_CHECK_LIST` | JSON | 垃圾邮件检查配置, 任何一项 `存在``不通过` 则被判定为垃圾邮件 | `["spf", "dkim", "dmarc"]` |
| `JUNK_MAIL_FORCE_PASS_LIST` | JSON | 垃圾邮件检查配置, 任何一项 `不存在` 或者 `不通过` 则被判定为垃圾邮件 | `["spf", "dkim", "dmarc"]` |
| `FORWARD_ADDRESS_LIST` | JSON | 全局转发地址列表,如果不配置则不启用,启用后所有邮件都会转发到列表中的地址 | `["xxx@xxx.com"]` |
| `REMOVE_EXCEED_SIZE_ATTACHMENT` | 文本/JSON | 如果附件大小超过 2MB则删除附件邮件可能由于解析而丢失一些信息 | `true` |
| `REMOVE_ALL_ATTACHMENT` | 文本/JSON | 移除所有附件,邮件可能由于解析而丢失一些信息 | `true` |
> [!NOTE]
> `垃圾邮件检查` 和 `移除附件功能` 需要解析邮件,免费版 CPU 有限,可能会导致大邮件解析超时
>
> 如果你想解析邮件能力更强
>
> 参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker)
## webhook 相关变量
| 变量名 | 类型 | 说明 | 示例 |
| ---------------- | --------- | ------------------------------------- | ------------------ |
| `ENABLE_WEBHOOK` | 文本/JSON | 是否启用 webhook | `true` |
| `FRONTEND_URL` | 文本 | 前端地址,用于发送 webhook 的邮件 url | `https://xxxx.xxx` |
> [!NOTE]
> webhook 功能需要解析邮件,免费版 CPU 有限,可能会导致大邮件解析超时
>
> 如果你想解析邮件能力更强
>
> 参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker)
## 用户相关变量
| 变量名 | 类型 | 说明 | 示例 |
| ------------------------------------- | --------- | ------------------------------------------------------------------------ | ------- |
| `USER_DEFAULT_ROLE` | 文本 | 新用户默认角色, 仅在启用邮件验证时有效 | `vip` |
| `ADMIN_USER_ROLE` | 文本 | admin 角色配置, 如果用户角色等于 ADMIN_USER_ROLE 则可以访问 admin 控制台 | `admin` |
| `USER_ROLES` | JSON | - | 见下方 |
| `DISABLE_ANONYMOUS_USER_CREATE_EMAIL` | 文本/JSON | 禁用匿名用户创建邮箱,如果设置为 true则用户只能在登录后创建邮箱地址 | `true` |
| `NO_LIMIT_SEND_ROLE` | 文本 | 可以无限发送邮件的角色 | `vip` |
> [!NOTE] USER_ROLES 用户角色配置说明
>
> - 如果 `domains` 为空将使用 `DEFAULT_DOMAINS`
> - 如果 prefix 为 null 将使用默认前缀, 如果 prefix 为空字符串将不使用前缀
>
> 通过用户界面部署时 `USER_ROLES` 请配置为此格式 `[{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"vip","prefix":"vip"},{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"admin","prefix":""}]`
>
> CLI 部署时 `USER_ROLES` 请参考 `worker/wrangler.toml.template` 配置为此格式 `[{ domains = ["awsl.uk", "dreamhunter2333.xyz"], role = "vip", prefix = "vip" }, { domains = ["awsl.uk", "dreamhunter2333.xyz"], role = "admin", prefix = "" }]`
## 网页相关变量
| 变量名 | 类型 | 说明 | 示例 |
| ------------------------- | ----------- | ------------------------------------------------ | --------------------- |
| `TITLE` | 文本 | 自定义前端页面网站标题,支持 html | `Custom Title` |
| `ANNOUNCEMENT` | 文本 | 自定义前端页面公告,支持 html | `Custom Announcement` |
| `COPYRIGHT` | 文本 | 自定义前端界面页脚文本,支持 html | `Dream Hunter` |
| `ADMIN_CONTACT` | 文本 | admin 联系方式,可配置任意字符串, 不配置则不显示 | `xxx@gmail.com` |
| `DISABLE_SHOW_GITHUB` | 文本/JSON | 是否显示 GitHub 链接 | `true` |
| `CF_TURNSTILE_SITE_KEY` | 文本/Secret | Turnstile 人机验证配置 | `xxx` |
| `CF_TURNSTILE_SECRET_KEY` | 文本/Secret | Turnstile 人机验证配置 | `xxx` |
## Telegram Bot 相关变量
| 变量名 | 类型 | 说明 | 示例 |
| ---------------- | ---- | ---------------------------------------------------------------------- | ---- |
| `TG_MAX_ADDRESS` | 数字 | telegram bot 最多绑定邮箱数量 | `5` |
| `TG_BOT_INFO` | 文本 | 可不配置telegram BOT_INFO预定义的 BOT_INFO 可以降低 webhook 的延迟 | `{}` |
> [!NOTE]
> Telegram 功能需要解析邮件,免费版 CPU 有限,可能会导致大邮件解析超时
>
> 如果你想解析邮件能力更强
>
> 参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker)
## 其他变量
| 变量名 | 类型 | 说明 | 示例 |
| ----------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `ENABLE_ANOTHER_WORKER` | 文本/JSON | 是否开启其他 worker 处理邮件 | `false` |
| `ANOTHER_WORKER_LIST` | JSON | - 其他 worker 处理邮件的配置,可以配置多个其他 worker <br/> - 通过关键词筛选,调用对应绑定的 worker 的方法(默认方法名为 rpcEmail<br/> - keywords必填否则 worker 将不会被触发 | 见下方 |
> [!NOTE]
> `ANOTHER_WORKER_LIST` 的配置示例
>
> ```toml
> #ANOTHER_WORKER_LIST ="""
> #[
> # {
> # "binding":"AUTH_INBOX",
> # "method":"rpcEmail",
> # "keywords":[
> # "验证码","激活码","激活链接","确认链接","验证邮箱","确认邮件","账号激活","邮件验证","账户确认","安全码","认证码","安全验证","登陆码","确认码","启用账户","激活账户","账号验证","注册确认",
> # "account","activation","verify","verification","activate","confirmation","email","code","validate","registration","login","code","expire","confirm"
> # ]
> # }
> #]
> #
> ```

View File

@@ -1,12 +1,12 @@
{
"name": "temp-mail-docs",
"private": true,
"version": "0.8.4",
"version": "0.8.6",
"type": "module",
"devDependencies": {
"@types/node": "^22.10.2",
"vitepress": "^1.5.0",
"wrangler": "^3.99.0"
"@types/node": "^22.10.7",
"vitepress": "^1.6.2",
"wrangler": "^3.104.0"
},
"scripts": {
"dev": "vitepress dev docs",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cloudflare_temp_email",
"version": "0.8.4",
"version": "0.8.6",
"private": true,
"type": "module",
"scripts": {
@@ -11,22 +11,22 @@
"build": "wrangler deploy --dry-run --outdir dist --minify"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20241224.0",
"@eslint/js": "9.17.0",
"@simplewebauthn/types": "^10.0.0",
"eslint": "9.17.0",
"@cloudflare/workers-types": "^4.20250121.0",
"@eslint/js": "9.18.0",
"@simplewebauthn/types": "10.0.0",
"eslint": "9.18.0",
"globals": "^15.14.0",
"typescript-eslint": "^8.18.2",
"wrangler": "^3.99.0"
"typescript-eslint": "^8.21.0",
"wrangler": "^3.104.0"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.717.0",
"@aws-sdk/s3-request-presigner": "^3.717.0",
"@simplewebauthn/server": "^10.0.1",
"hono": "^4.6.14",
"mimetext": "^3.0.24",
"postal-mime": "^2.3.2",
"resend": "^4.0.1",
"@aws-sdk/client-s3": "^3.732.0",
"@aws-sdk/s3-request-presigner": "^3.732.0",
"@simplewebauthn/server": "10.0.1",
"hono": "^4.6.17",
"mimetext": "^3.0.27",
"postal-mime": "^2.4.1",
"resend": "^4.1.1",
"telegraf": "4.16.3"
},
"pnpm": {

2032
worker/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ export default {
"HAS_ADMIN_PASSWORDS": getAdminPasswords(c).length,
"ANNOUNCEMENT": getStringValue(c.env.ANNOUNCEMENT),
"PREFIX": c.env.PREFIX,
"PREFIX": getStringValue(c.env.PREFIX),
"ADDRESS_CHECK_REGEX": getStringValue(c.env.ADDRESS_CHECK_REGEX),
"ADDRESS_REGEX": getStringValue(c.env.ADDRESS_REGEX),
"MIN_ADDRESS_LEN": getIntValue(c.env.MIN_ADDRESS_LEN, 1),
@@ -46,6 +46,9 @@ export default {
"JUNK_MAIL_CHECK_LIST": getStringArray(c.env.JUNK_MAIL_CHECK_LIST),
"JUNK_MAIL_FORCE_PASS_LIST": getStringArray(c.env.JUNK_MAIL_FORCE_PASS_LIST),
"REMOVE_EXCEED_SIZE_ATTACHMENT": getBooleanValue(c.env.REMOVE_EXCEED_SIZE_ATTACHMENT),
"REMOVE_ALL_ATTACHMENT": getBooleanValue(c.env.REMOVE_ALL_ATTACHMENT),
"ENABLE_ANOTHER_WORKER": getBooleanValue(c.env.ENABLE_ANOTHER_WORKER),
"ANOTHER_WORKER_LIST": getAnotherWorkerList(c),
})

View File

@@ -18,7 +18,7 @@ api.get('/open_api/settings', async (c) => {
return c.json({
"title": c.env.TITLE,
"announcement": getStringValue(c.env.ANNOUNCEMENT),
"prefix": c.env.PREFIX,
"prefix": getStringValue(c.env.PREFIX),
"addressRegex": getStringValue(c.env.ADDRESS_REGEX),
"minAddressLen": getIntValue(c.env.MIN_ADDRESS_LEN, 1),
"maxAddressLen": getIntValue(c.env.MAX_ADDRESS_LEN, 30),

View File

@@ -277,7 +277,7 @@ export const commonParseMail = async (parsedEmailContext: ParsedEmailContext): P
// const { parse_message_wrapper } = await import('mail-parser-wasm-worker');
// const parsedEmail = parse_message_wrapper(raw_mail);
// return {
// parsedEmailContext.parsedEmail = {
// sender: parsedEmail.sender || "",
// subject: parsedEmail.subject || "",
// text: parsedEmail.text || "",
@@ -286,19 +286,21 @@ export const commonParseMail = async (parsedEmailContext: ParsedEmailContext): P
// ) || [],
// html: parsedEmail.body_html || "",
// };
// return parsedEmailContext.parsedEmail;
// } catch (e) {
// console.error("Failed use mail-parser-wasm-worker to parse email", e);
// }
try {
const { default: PostalMime } = await import('postal-mime');
const parsedEmail = await PostalMime.parse(raw_mail);
return {
parsedEmailContext.parsedEmail = {
sender: parsedEmail.from ? `${parsedEmail.from.name} <${parsedEmail.from.address}>` : "",
subject: parsedEmail.subject || "",
text: parsedEmail.text || "",
html: parsedEmail.html || "",
headers: parsedEmail.headers || [],
};
return parsedEmailContext.parsedEmail;
}
catch (e) {
console.error("Failed use PostalMime to parse email", e);
@@ -319,13 +321,13 @@ export const commonGetUserRole = async (
export const getAddressPrefix = async (c: Context<HonoCustomType>): Promise<string | undefined> => {
const user = c.get("userPayload");
if (!user) {
return c.env.PREFIX;
return getStringValue(c.env.PREFIX);
}
const user_role = await commonGetUserRole(c, user.user_id);
if (typeof user_role?.prefix === "string") {
return user_role.prefix;
}
return c.env.PREFIX;
return getStringValue(c.env.PREFIX);
}
export const getAllowDomains = async (c: Context<HonoCustomType>): Promise<string[]> => {
@@ -451,9 +453,17 @@ export async function triggerAnotherWorker(
console.log(`worker.binding = ${bindingName} not match keywords, parsedText = ${parsedText}`);
continue;
}
try {
const requestBody = JSON.stringify(rpcEmailMessage);
const bodyObj = { ...rpcEmailMessage } as any;
if (bodyObj.headers && typeof bodyObj.headers.forEach === "function") {
const headerObj: any = {}
bodyObj.headers.forEach((value: string, key: string) => {
headerObj[key] = value;
});
bodyObj.headers = headerObj
}
const requestBody = JSON.stringify(bodyObj);
console.log(`exec worker , binding = ${bindingName} , requestBody = ${requestBody}`);
await method(requestBody);
} catch (e1) {
console.error(`execute method = ${methodName} error`, e1);

View File

@@ -1,5 +1,5 @@
export const CONSTANTS = {
VERSION: 'v0.8.4',
VERSION: 'v0.8.6',
// DB settings
ADDRESS_BLOCK_LIST_KEY: 'address_block_list',

View File

@@ -0,0 +1,52 @@
import { Bindings, ParsedEmailContext } from "../types";
import { getBooleanValue } from "../utils";
import { commonParseMail } from "../common";
import { createMimeMessage } from "mimetext";
export const remove_attachment_if_need = async (
env: Bindings,
parsedEmailContext: ParsedEmailContext,
from_address: string,
to_address: string,
size: number
): Promise<void> => {
// if configured, remove all attachment
const removeAllAttachment = getBooleanValue(env.REMOVE_ALL_ATTACHMENT);
// if attachment size > 2MB, remove attachment
const removeExceedSizeAttachment = getBooleanValue(env.REMOVE_EXCEED_SIZE_ATTACHMENT) && size >= 2 * 1024 * 1024;
const shouldRemoveAttachment = removeAllAttachment || removeExceedSizeAttachment;
if (!shouldRemoveAttachment) return;
const parsedEmail = await commonParseMail(parsedEmailContext);
if (!parsedEmail) return;
const msg = createMimeMessage();
if (parsedEmail?.headers) {
for (const header of parsedEmail.headers) {
try {
msg.setHeader(header["key"], header["value"]);
} catch (error) {
// ignore
}
}
}
msg.setSender({
name: parsedEmail?.sender || from_address,
addr: from_address
});
msg.setRecipient(to_address);
msg.setSubject(parsedEmail?.subject || "Failed to parse email subject");
if (parsedEmail?.html) {
msg.addMessage({
contentType: 'text/html',
data: parsedEmail.html
});
}
if (parsedEmail?.text) {
msg.addMessage({
contentType: 'text/plain',
data: parsedEmail.text
});
}
parsedEmailContext.rawEmail = msg.asRaw();
}

View File

@@ -7,6 +7,7 @@ import { auto_reply } from "./auto_reply";
import { isBlocked } from "./black_list";
import { triggerWebhook, triggerAnotherWorker, commonParseMail } from "../common";
import { check_if_junk_mail } from "./check_junk";
import { remove_attachment_if_need } from "./check_attachment";
async function email(message: ForwardableEmailMessage, env: Bindings, ctx: ExecutionContext) {
@@ -29,7 +30,14 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
return;
}
} catch (error) {
console.log("check junk mail error", error);
console.error("check junk mail error", error);
}
// remove attachment if configured or size > 2MB
try {
await remove_attachment_if_need(env, parsedEmailContext, message.from, message.to, message.rawSize);
} catch (error) {
console.error("remove attachment error", error);
}
const message_id = message.headers.get("Message-ID");
@@ -38,15 +46,15 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
const { success } = await env.DB.prepare(
`INSERT INTO raw_mails (source, address, raw, message_id) VALUES (?, ?, ?, ?)`
).bind(
message.from, message.to, rawEmail, message_id
message.from, message.to, parsedEmailContext.rawEmail, message_id
).run();
if (!success) {
message.setReject(`Failed save message to ${message.to}`);
console.log(`Failed save message from ${message.from} to ${message.to}`);
console.error(`Failed save message from ${message.from} to ${message.to}`);
}
}
catch (error) {
console.log("save email error", error);
console.error("save email error", error);
}
// forward email
@@ -56,7 +64,7 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
await message.forward(forwardAddress);
}
} catch (error) {
console.log("forward email error", error);
console.error("forward email error", error);
}
// send email to telegram
@@ -65,7 +73,7 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
{ env: env } as Context<HonoCustomType>,
message.to, parsedEmailContext, message_id);
} catch (error) {
console.log("send mail to telegram error", error);
console.error("send mail to telegram error", error);
}
// send webhook
@@ -75,21 +83,18 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
message.to, parsedEmailContext, message_id
);
} catch (error) {
console.log("send webhook error", error);
console.error("send webhook error", error);
}
// trigger another worker
try {
const headersMap = new Map<string, string>();
if (message.headers) {
message.headers.forEach((value, key) => { headersMap.set(key, value); });
}
const parsedText = (await commonParseMail(parsedEmailContext))?.text ?? ""
const parsedEmail = (await commonParseMail(parsedEmailContext));
const parsedText = parsedEmail?.text ?? ""
const rpcEmail: RPCEmailMessage = {
from: message.from,
to: message.to,
rawEmail: rawEmail,
headers: headersMap
headers: message.headers
}
await triggerAnotherWorker({ env: env } as Context<HonoCustomType>, rpcEmail, parsedText);
} catch (error) {

View File

@@ -338,8 +338,8 @@ export async function sendMailToTelegram(
return;
}
const settings = await c.env.KV.get<TelegramSettings>(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
const golbalPush = settings?.enableGlobalMailPush && settings?.globalMailPushList;
if (!userId && !golbalPush) {
const globalPush = settings?.enableGlobalMailPush && settings?.globalMailPushList;
if (!userId && !globalPush) {
return;
}
const mailId = await c.env.DB.prepare(
@@ -353,7 +353,7 @@ export async function sendMailToTelegram(
url.searchParams.set("mail_id", mailId);
miniAppButtons.push(Markup.button.webApp("查看邮件", url.toString()));
}
if (golbalPush) {
if (globalPush) {
for (const pushId of settings.globalMailPushList) {
await bot.telegram.sendMessage(pushId, mail, {
...Markup.inlineKeyboard([

View File

@@ -50,6 +50,8 @@ export type Bindings = {
ENABLE_ANOTHER_WORKER: string | boolean | undefined
ANOTHER_WORKER_LIST: string | AnotherWorker[] | undefined
REMOVE_ALL_ATTACHMENT: string | boolean | undefined
REMOVE_EXCEED_SIZE_ATTACHMENT: string | boolean | undefined
// s3 config
S3_ENDPOINT: string | undefined
@@ -108,7 +110,7 @@ type RPCEmailMessage = {
from: string | undefined | null,
to: string | undefined | null,
rawEmail: string | undefined | null,
headers: Map<string, string>,
headers: object | undefined | null,
}
type ParsedEmailContext = {

View File

@@ -12,7 +12,7 @@ import { api as telegramApi } from './telegram_api'
import { email } from './email';
import { scheduled } from './scheduled';
import { getAdminPasswords, getPasswords, getBooleanValue } from './utils';
import { getAdminPasswords, getPasswords, getBooleanValue, getStringArray } from './utils';
import { HonoCustomType, UserPayload } from './types';
const app = new Hono<HonoCustomType>()
@@ -210,14 +210,21 @@ app.route('/', adminApi)
app.route('/', apiSendMail)
app.route('/', telegramApi)
app.get('/', async c => {
if (!c.env.DB) { return c.text("DB is not available", 400); }
const health_check = async (c: Context<HonoCustomType>) => {
if (!c.env.DB) {
return c.text("DB is not available", 400);
}
if (!c.env.JWT_SECRET) {
return c.text("JWT_SECRET is not set", 400);
}
if (getStringArray(c.env.DOMAINS).length === 0) {
return c.text("DOMAINS is not set", 400);
}
return c.text("OK");
})
app.get('/health_check', async c => {
if (!c.env.DB) { return c.text("DB is not available", 400); }
return c.text("OK");
})
}
app.get('/', health_check)
app.get('/health_check', health_check)
app.all('/*', async c => c.text("Not Found", 404))

View File

@@ -80,10 +80,14 @@ ENABLE_AUTO_REPLY = false
# JUNK_MAIL_CHECK_LIST = = ["spf", "dkim", "dmarc"]
# junk mail force check pass list, if no status or status is not pass, will be marked as junk mail
# JUNK_MAIL_FORCE_PASS_LIST = ["spf", "dkim", "dmarc"]
# remove attachment if size exceed 2MB, mail maybe mising some information due to parsing
# REMOVE_EXCEED_SIZE_ATTACHMENT = true
# remove all attachment, mail maybe mising some information due to parsing
# REMOVE_ALL_ATTACHMENT = true
# Calling other woker to process email
#ENABLE_ANOTHER_WORKER = false
#ANOTHER_WORKER_LIST ="""
#[
# ENABLE_ANOTHER_WORKER = false
# ANOTHER_WORKER_LIST = """
# [
# {
# "binding":"AUTH_INBOX",
# "method":"rpcEmail",
@@ -92,8 +96,8 @@ ENABLE_AUTO_REPLY = false
# "account","activation","verify","verification","activate","confirmation","email","code","validate","registration","login","code","expire","confirm"
# ]
# }
#]
#"""
# ]
# """
[[d1_databases]]
binding = "DB"