├── frontend ├── .env.pages ├── public │ ├── logo.png │ └── favicon.ico ├── .env.example ├── src │ ├── constant │ │ └── index.ts │ ├── utils │ │ ├── composables.js │ │ ├── index.ts │ │ └── fingerprint.ts │ ├── main.js │ ├── i18n.ts │ ├── models │ │ └── index.ts │ ├── views │ │ ├── admin │ │ │ ├── MailsUnknow.vue │ │ │ ├── MailWebhook.vue │ │ │ ├── WorkerConfig.vue │ │ │ ├── SendBox.vue │ │ │ ├── Mails.vue │ │ │ └── UserAddressManagement.vue │ │ ├── common │ │ │ ├── AdminContact.vue │ │ │ └── About.vue │ │ ├── index │ │ │ └── Webhook.vue │ │ ├── user │ │ │ ├── BindAddress.vue │ │ │ ├── UserOauth2Callback.vue │ │ │ ├── UserBar.vue │ │ │ └── UserMailBox.vue │ │ ├── Footer.vue │ │ ├── User.vue │ │ └── telegram │ │ │ └── Mail.vue │ ├── router │ │ └── index.js │ └── components │ │ ├── ShadowHtmlComponent.vue │ │ └── Turnstile.vue ├── tsconfig.json ├── .gitignore ├── README.md ├── index.html ├── vite.config.js └── package.json ├── .flake8 ├── db ├── 2024-01-13-patch.sql ├── 2025-09-23-patch.sql ├── 2024-04-03-patch.sql ├── 2024-05-01-patch.sql ├── 2024-04-09-patch.sql ├── 2025-12-06-metadata.sql ├── 2024-07-14-patch.sql ├── 2024-04-12-patch.sql ├── 2024-08-10-patch.sql └── 2024-05-08-patch.sql ├── smtp_proxy_server ├── .env.example ├── requirements.txt ├── dockerfile ├── models.py ├── docker-compose.yaml ├── config.py ├── main.py └── parse_email.py ├── worker ├── .prettierrc ├── .editorconfig ├── tsconfig.json ├── eslint.config.js ├── src │ ├── email │ │ ├── black_list.ts │ │ ├── auto_reply.ts │ │ ├── check_attachment.ts │ │ └── check_junk.ts │ ├── admin_api │ │ ├── send_mail.ts │ │ ├── webhook_settings.ts │ │ ├── ai_extract_settings.ts │ │ ├── cleanup_api.ts │ │ ├── oauth2_settings.ts │ │ ├── admin_mail_api.ts │ │ └── mail_webhook_settings.ts │ ├── i18n │ │ ├── index.ts │ │ ├── type.ts │ │ └── zh.ts │ ├── constants.ts │ ├── telegram_api │ │ ├── settings.ts │ │ └── index.ts │ ├── user_api │ │ ├── index.ts │ │ └── user_mail_api.ts │ ├── mails_api │ │ ├── auto_reply.ts │ │ ├── address_auth.ts │ │ └── webhook_settings.ts │ ├── commom_api.ts │ └── scheduled.ts ├── package.json └── .gitignore ├── vitepress-docs ├── docs │ ├── public │ │ ├── logo.png │ │ ├── feature │ │ │ ├── admin.png │ │ │ ├── imap.png │ │ │ ├── oauth2.png │ │ │ ├── s3-save.png │ │ │ ├── telegram.png │ │ │ ├── s3-download.png │ │ │ ├── oauth2-login.png │ │ │ ├── address-webhook.png │ │ │ ├── admin-user-page.png │ │ │ ├── admin-mail-webhook.png │ │ │ ├── admin-user-management.png │ │ │ ├── admin-webhook-settings.png │ │ │ ├── another-worker-enhanced-01.png │ │ │ ├── another-worker-enhanced-02.png │ │ │ ├── another-worker-enhanced-03.png │ │ │ └── another-worker-enhanced-04.png │ │ ├── ui_install │ │ │ ├── d1.png │ │ │ ├── pages.png │ │ │ ├── d1-exec.png │ │ │ ├── pages-1.png │ │ │ ├── worker-1.png │ │ │ ├── worker-2.png │ │ │ ├── worker-3.png │ │ │ ├── worker-d1.png │ │ │ ├── worker-kv.png │ │ │ ├── worker-var.png │ │ │ ├── pages-domain.png │ │ │ ├── worker-d1-1.png │ │ │ ├── worker-d1-2.png │ │ │ ├── worker-kv-0.png │ │ │ ├── worker-kv-1.png │ │ │ ├── worker-kv-2.png │ │ │ ├── worker-upload.png │ │ │ ├── worker_home.png │ │ │ ├── worker-bindings.png │ │ │ └── worker-runtime.png │ │ └── readme_assets │ │ │ ├── d1.png │ │ │ ├── demo.png │ │ │ ├── email.png │ │ │ ├── pages.png │ │ │ └── worker.png │ ├── reference.md │ ├── en │ │ ├── reference.md │ │ ├── guide │ │ │ ├── actions │ │ │ │ ├── pre-requisite.md │ │ │ │ ├── d1.md │ │ │ │ └── auto-update.md │ │ │ ├── feature │ │ │ │ ├── admin-user-management.md │ │ │ │ ├── subdomain.md │ │ │ │ ├── admin.md │ │ │ │ ├── google-ads.md │ │ │ │ ├── user-oauth2.md │ │ │ │ ├── s3-attachment.md │ │ │ │ ├── mail-api.md │ │ │ │ ├── config-smtp-proxy.md │ │ │ │ ├── webhook.md │ │ │ │ ├── send-mail-api.md │ │ │ │ ├── telegram.md │ │ │ │ └── new-address-api.md │ │ │ ├── cli │ │ │ │ ├── pre-requisite.md │ │ │ │ ├── d1.md │ │ │ │ └── pages.md │ │ │ ├── star-history.md │ │ │ ├── email-routing.md │ │ │ ├── what-is-temp-mail.md │ │ │ ├── ui │ │ │ │ └── d1.md │ │ │ └── quick-start.md │ │ ├── index.md │ │ └── status.md │ ├── zh │ │ └── guide │ │ │ ├── what-is-temp-mail.md │ │ │ ├── actions │ │ │ ├── pre-requisite.md │ │ │ ├── auto-update.md │ │ │ └── d1.md │ │ │ ├── feature │ │ │ ├── admin-user-management.md │ │ │ ├── subdomain.md │ │ │ ├── admin.md │ │ │ ├── user-oauth2.md │ │ │ ├── google-ads.md │ │ │ ├── s3-attachment.md │ │ │ ├── webhook.md │ │ │ ├── config-smtp-proxy.md │ │ │ ├── mail-api.md │ │ │ ├── telegram.md │ │ │ ├── send-mail-api.md │ │ │ ├── ai-extract.md │ │ │ ├── mail_parser_wasm_worker.md │ │ │ └── new-address-api.md │ │ │ ├── email-routing.md │ │ │ ├── cli │ │ │ ├── pre-requisite.md │ │ │ ├── d1.md │ │ │ └── pages.md │ │ │ ├── star-history.md │ │ │ ├── ui │ │ │ └── d1.md │ │ │ ├── quick-start.md │ │ │ ├── config-send-mail.md │ │ │ └── common-issues.md │ ├── index.md │ ├── status.md │ └── .vitepress │ │ └── config.ts └── package.json ├── .vscode └── extensions.json ├── pages ├── wrangler.toml ├── functions │ └── _middleware.js ├── .gitignore └── package.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug-反馈.md │ └── feature-request.md ├── workflows │ ├── sync.yaml │ ├── pr_agent.yml │ ├── frontend_pagefunction_deploy.yaml │ ├── smtp_proxy_server.yml │ ├── tag_build.yml │ ├── docs_deploy.yml │ ├── frontend_deploy.yaml │ └── backend_deploy.yaml └── config │ └── mail-parser-wasm-worker.patch ├── mail-parser-wasm ├── Cargo.toml ├── worker │ ├── index.d.ts │ ├── index.js │ └── package.json ├── .gitignore └── README.md ├── scripts └── update-dependencies.sh ├── LICENSE ├── README_EN.md └── .gitignore /frontend/.env.pages: -------------------------------------------------------------------------------- 1 | VITE_API_BASE= 2 | VITE_CF_WEB_ANALY_TOKEN= 3 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 180 3 | exclude = .git,__pycache__,build,dist 4 | -------------------------------------------------------------------------------- /db/2024-01-13-patch.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE 2 | mails 3 | ADD 4 | message_id TEXT; 5 | -------------------------------------------------------------------------------- /db/2025-09-23-patch.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE 2 | address 3 | ADD 4 | password TEXT; 5 | -------------------------------------------------------------------------------- /db/2024-04-03-patch.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE 2 | address 3 | ADD 4 | updated_at DATETIME; 5 | -------------------------------------------------------------------------------- /smtp_proxy_server/.env.example: -------------------------------------------------------------------------------- 1 | proxy_url=https://temp-email-api.xxx.xxx 2 | port=8025 3 | -------------------------------------------------------------------------------- /frontend/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/frontend/public/logo.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/frontend/public/favicon.ico -------------------------------------------------------------------------------- /worker/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140, 3 | "singleQuote": true, 4 | "semi": true, 5 | "useTabs": true 6 | } 7 | -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | VITE_API_BASE=https://temp-email-api.xxx.xxx 2 | VITE_CF_WEB_ANALY_TOKEN= 3 | VITE_IS_TELEGRAM=false 4 | -------------------------------------------------------------------------------- /vitepress-docs/docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/logo.png -------------------------------------------------------------------------------- /smtp_proxy_server/requirements.txt: -------------------------------------------------------------------------------- 1 | aiosmtpd==1.4.6 2 | pydantic-settings==2.9.1 3 | requests==2.32.4 4 | Twisted==25.5.0 5 | httpx==0.28.1 6 | -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/admin.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/imap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/imap.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/oauth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/oauth2.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/d1.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/s3-save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/s3-save.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/telegram.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/readme_assets/d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/readme_assets/d1.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/pages.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/s3-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/s3-download.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/readme_assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/readme_assets/demo.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/readme_assets/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/readme_assets/email.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/readme_assets/pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/readme_assets/pages.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/d1-exec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/d1-exec.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/pages-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/pages-1.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-1.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-2.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-3.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/oauth2-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/oauth2-login.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/readme_assets/worker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/readme_assets/worker.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-d1.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-kv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-kv.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-var.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-var.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/address-webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/address-webhook.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/admin-user-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/admin-user-page.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/pages-domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/pages-domain.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-d1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-d1-1.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-d1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-d1-2.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-kv-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-kv-0.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-kv-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-kv-1.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-kv-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-kv-2.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-upload.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker_home.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/admin-mail-webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/admin-mail-webhook.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-bindings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-bindings.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/ui_install/worker-runtime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/ui_install/worker-runtime.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/admin-user-management.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/admin-user-management.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/admin-webhook-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/admin-webhook-settings.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-python.vscode-pylance", 4 | "1yib.rust-bundle", 5 | "rust-lang.rust-analyzer", 6 | "vue.volar" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/another-worker-enhanced-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/another-worker-enhanced-01.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/another-worker-enhanced-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/another-worker-enhanced-02.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/another-worker-enhanced-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/another-worker-enhanced-03.png -------------------------------------------------------------------------------- /vitepress-docs/docs/public/feature/another-worker-enhanced-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0ise/cloudflare_temp_email/main/vitepress-docs/docs/public/feature/another-worker-enhanced-04.png -------------------------------------------------------------------------------- /smtp_proxy_server/dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | COPY requirements.txt /requirements.txt 5 | RUN python3 -m pip install -r /requirements.txt 6 | COPY . /app 7 | ENTRYPOINT [ "python3", "main.py" ] 8 | -------------------------------------------------------------------------------- /db/2024-05-01-patch.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS settings ( 2 | key TEXT PRIMARY KEY, 3 | value TEXT, 4 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 5 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP 6 | ); 7 | -------------------------------------------------------------------------------- /db/2024-04-09-patch.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS raw_mails ( 2 | id INTEGER PRIMARY KEY, 3 | message_id TEXT, 4 | source TEXT, 5 | address TEXT, 6 | raw TEXT, 7 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP 8 | ); 9 | -------------------------------------------------------------------------------- /frontend/src/constant/index.ts: -------------------------------------------------------------------------------- 1 | const COMMOM_MAIL = [ 2 | "gmail.com", "163.com", "126.com", "qq.com", "outlook.com", "hotmail.com", 3 | "icloud.com", "yahoo.com", "foxmail.com" 4 | ] 5 | 6 | export default { 7 | COMMOM_MAIL 8 | } 9 | -------------------------------------------------------------------------------- /vitepress-docs/docs/reference.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 | - https://developers.cloudflare.com/d1/ 4 | - https://developers.cloudflare.com/pages/ 5 | - https://developers.cloudflare.com/workers/ 6 | - https://developers.cloudflare.com/email-routing/ 7 | -------------------------------------------------------------------------------- /pages/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "temp-email-pages" 2 | pages_build_output_dir = "../frontend/dist" 3 | compatibility_date = "2024-05-13" 4 | 5 | [[services]] 6 | binding = "BACKEND" 7 | service = "cloudflare_temp_email" 8 | environment = "production" 9 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/reference.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 | - https://developers.cloudflare.com/d1/ 4 | - https://developers.cloudflare.com/pages/ 5 | - https://developers.cloudflare.com/workers/ 6 | - https://developers.cloudflare.com/email-routing/ 7 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/what-is-temp-mail.md: -------------------------------------------------------------------------------- 1 | # 临时邮箱简介 2 | 3 | ## 什么是临时邮箱 4 | 5 | 临时邮箱,也被称为一次性邮箱或临时邮件地址,是一种用于临时接收邮件的虚拟邮箱。与常规邮箱不同,临时邮箱旨在提供一种匿名且临时的邮件接收解决方案。 6 | 7 | 临时邮箱往往由网站或在线服务提供商提供,用户可以在需要注册或接收验证邮件时使用临时邮箱地址,而无需暴露自己的真实邮箱地址。这样做的好处是可以保护个人隐私 8 | -------------------------------------------------------------------------------- /db/2025-12-06-metadata.sql: -------------------------------------------------------------------------------- 1 | -- Add metadata column to raw_mails table for storing AI extraction results and other metadata 2 | -- This column stores JSON data with flexible schema for various analysis results 3 | 4 | ALTER TABLE raw_mails ADD COLUMN metadata TEXT; 5 | -------------------------------------------------------------------------------- /frontend/src/utils/composables.js: -------------------------------------------------------------------------------- 1 | import { useBreakpoint, useMemo } from 'vooks' 2 | 3 | export function useIsMobile() { 4 | const breakpointRef = useBreakpoint() 5 | return useMemo(() => { 6 | return breakpointRef.value === 'xs' 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/actions/pre-requisite.md: -------------------------------------------------------------------------------- 1 | # Gihub Actions 部署准备 2 | 3 | ## GitHub 账户 4 | 5 | - 需要一个 GitHub 账户 6 | - 良好的网络环境 7 | 8 | ## Fork 仓库 9 | 10 | - 在 GitHub fork [本仓库](https://github.com/dreamhunter2333/cloudflare_temp_email.git) 11 | -------------------------------------------------------------------------------- /smtp_proxy_server/models.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | from pydantic import BaseModel 3 | 4 | 5 | class EmailModel(BaseModel): 6 | headers: Dict[str, str] 7 | body: str 8 | content_type: str 9 | subparts: List["EmailModel"] 10 | size: int 11 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/admin-user-management.md: -------------------------------------------------------------------------------- 1 | # Admin 用户相关 2 | 3 | ## 用户管理页面 4 | 5 | ![admin-user-management](/feature/admin-user-management.png) 6 | 7 | ## 用户设置 8 | 9 | 此处开启用户登录,以及验证等配置 10 | 11 | ![admin-user-page](/feature/admin-user-page.png) 12 | -------------------------------------------------------------------------------- /worker/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.yml] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "lib": [ 9 | "ESNext" 10 | ], 11 | "types": [] 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-反馈.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 反馈 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 复现步骤 11 | 12 | 13 | 14 | ## 预期行为 15 | 16 | 17 | 18 | ## 部署方式 19 | 20 | - [ ] cli 部署 21 | - [ ] 用户界面部署 22 | 23 | ## 浏览器环境 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "[Feature]" 5 | labels: enhancement, good first issue 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 请描述您的 Feature 11 | 12 | ## 描述您想要的解决方案 13 | 14 | ## 描述您考虑过的替代方案 15 | 16 | ## 附加上下文 17 | -------------------------------------------------------------------------------- /mail-parser-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mail-parser-wasm" 3 | version = "0.2.1" 4 | edition = "2021" 5 | description = "A simple mail parser for wasm" 6 | license = "MIT" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | mail-parser = "0.9.4" 13 | wasm-bindgen = "0.2.99" 14 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/subdomain.md: -------------------------------------------------------------------------------- 1 | # 配置子域名邮箱 2 | 3 | ::: warning 注意 4 | 子域名邮箱发送邮件可能无法发送邮件,建议使用主域名邮箱发送邮件,子域名邮箱仅用于接收邮件。 5 | 6 | mail channel 已不被支持,下面参考中仅限收件部分。 7 | ::: 8 | 9 | 参考 10 | 11 | - [配置子域名邮箱](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/164#issuecomment-2082612710) 12 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/actions/pre-requisite.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions Deployment Prerequisites 2 | 3 | ## GitHub Account 4 | 5 | - A GitHub account is required 6 | - A stable network connection 7 | 8 | ## Fork Repository 9 | 10 | - Fork [this repository](https://github.com/dreamhunter2333/cloudflare_temp_email.git) on GitHub 11 | -------------------------------------------------------------------------------- /worker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "lib": [ 9 | "ESNext" 10 | ], 11 | "types": [ 12 | "@cloudflare/workers-types" 13 | ] 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/admin-user-management.md: -------------------------------------------------------------------------------- 1 | # Admin User Management 2 | 3 | ## User Management Page 4 | 5 | ![admin-user-management](/feature/admin-user-management.png) 6 | 7 | ## User Settings 8 | 9 | Configure user login and authentication settings here 10 | 11 | ![admin-user-page](/feature/admin-user-page.png) 12 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { createHead } from '@unhead/vue' 3 | 4 | import App from './App.vue' 5 | import router from './router' 6 | import i18n from './i18n' 7 | 8 | const head = createHead() 9 | const app = createApp(App) 10 | app.use(i18n) 11 | app.use(router) 12 | app.use(head) 13 | app.mount('#app') 14 | -------------------------------------------------------------------------------- /scripts/update-dependencies.sh: -------------------------------------------------------------------------------- 1 | cd frontend/ 2 | pnpm up 3 | pnpm add -D wrangler@latest 4 | cd .. 5 | 6 | cd worker/ 7 | pnpm up 8 | pnpm add -D wrangler@latest 9 | cd .. 10 | 11 | cd pages/ 12 | pnpm up 13 | pnpm add -D wrangler@latest 14 | cd .. 15 | 16 | cd vitepress-docs/ 17 | pnpm up --latest 18 | pnpm add -D wrangler@latest 19 | cd .. 20 | -------------------------------------------------------------------------------- /db/2024-07-14-patch.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS user_roles ( 2 | id INTEGER PRIMARY KEY, 3 | user_id INTEGER UNIQUE NOT NULL, 4 | role_text TEXT, 5 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 6 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP 7 | ); 8 | 9 | CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id); 10 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/email-routing.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Email Routing 2 | 3 | 1. 在 CF 控制台网页的对应域名的 `Email Routing` 下,配置 `电子邮件 DNS 记录`, 如果是多个域名,需要配置多个域名的 `电子邮件 DNS 记录` 4 | 5 | 2. 在将电子邮件地址绑定到您的 Worker 之前,您需要启用电子邮件路由并拥有至少一个经过验证的电子邮件地址(目标地址)。 6 | 7 | 3. 配置每个域名的 `Email Routing` 的路由规则中的 `Catch-all 地址` 发送到 `worker` 8 | 9 | ![email](/readme_assets/email.png) 10 | -------------------------------------------------------------------------------- /mail-parser-wasm/worker/index.d.ts: -------------------------------------------------------------------------------- 1 | import initAsync, { MessageResult } from './mail_parser_wasm'; 2 | import MODULE from './mail_parser_wasm_bg.wasm'; 3 | export { initAsync, MODULE }; 4 | export * from './mail_parser_wasm'; 5 | /** 6 | * @param {string} raw_message 7 | * @returns {MessageResult} 8 | */ 9 | export function parse_message_wrapper(raw_message: string): MessageResult; 10 | -------------------------------------------------------------------------------- /frontend/src/i18n.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n' 2 | 3 | const i18n = createI18n({ 4 | legacy: false, // you must set `false`, to use Composition API 5 | locale: 'zh', // set locale 6 | fallbackLocale: 'en', // set fallback locale 7 | 'en': { 8 | messages: {} 9 | }, 10 | 'zh': { 11 | messages: {} 12 | } 13 | }) 14 | 15 | export default i18n; 16 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/cli/pre-requisite.md: -------------------------------------------------------------------------------- 1 | # 先决条件 2 | 3 | ## wrangler 的安装 4 | 5 | 安装 wrangler 6 | 7 | ```bash 8 | npm install wrangler -g 9 | ``` 10 | 11 | ## 克隆项目 12 | 13 | ```bash 14 | git clone https://github.com/dreamhunter2333/cloudflare_temp_email.git 15 | # 切换到最新 tag 或者你想部署的分支,你也可以直接使用 main 分支 16 | # git checkout $(git describe --tags $(git rev-list --tags --max-count=1)) 17 | ``` 18 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/actions/auto-update.md: -------------------------------------------------------------------------------- 1 | # Github Actions 部署如何配置自动更新 2 | 3 | ::: warning 注意 4 | 有问题请通过 `Github Issues` 反馈,感谢。 5 | 自动更新不会执行 D1 数据库的 sql 文件,当数据库 schema 变动时,需要手动执行。 6 | ::: 7 | 8 | 1. 打开仓库的 `Actions` 页面,找到 `Upstream Sync`,点击 `enable workflow` 启用 `workflow` 9 | 2. 如果 `Upstream Sync` 运行失败,到仓库主页点击 `Sync` 手动同步即可 10 | 3. 修改 `Upstream Sync` 的 `schedule` 配置可自定义更新间隔,参考 [cron 表达式](https://crontab.guru/) 11 | -------------------------------------------------------------------------------- /mail-parser-wasm/worker/index.js: -------------------------------------------------------------------------------- 1 | import initAsync, { initSync, parse_message } from './mail_parser_wasm'; 2 | import MODULE from './mail_parser_wasm_bg.wasm'; 3 | 4 | initSync({ module: MODULE }); 5 | 6 | 7 | export { initAsync, MODULE }; 8 | export * from './mail_parser_wasm'; 9 | export const parse_message_wrapper = (raw_message) => { 10 | initSync({ module: MODULE }); 11 | return parse_message(raw_message); 12 | } 13 | -------------------------------------------------------------------------------- /db/2024-04-12-patch.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS address_sender ( 2 | id INTEGER PRIMARY KEY, 3 | address TEXT UNIQUE, 4 | balance INTEGER DEFAULT 0, 5 | enabled INTEGER DEFAULT 1, 6 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP 7 | ); 8 | 9 | CREATE TABLE IF NOT EXISTS sendbox ( 10 | id INTEGER PRIMARY KEY, 11 | address TEXT, 12 | raw TEXT, 13 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP 14 | ); 15 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/actions/d1.md: -------------------------------------------------------------------------------- 1 | # 初始化/更新 D1 数据库 2 | 3 | ## 创建数据库 4 | 5 | 打开 cloudflare 控制台,选择 `Workers & Pages` -> `D1` -> `Create Database`,点击创建数据库 6 | 7 | ![d1](/ui_install/d1.png) 8 | 9 | 创建完成后,我们在 cloudflare 的控制台可以看到 D1 数据库,并获取到数据库的 `名称` 和 `数据库 ID` 10 | 11 | ## 初始化数据库 12 | 13 | 在部署完成后,在 admin 页面的 `快速设置` -> `数据库` 中,点击 `初始化数据库` 按钮来初始化数据库 14 | 15 | ## 更新数据库 schema 16 | 17 | 参考 [命令行更新 d1](/zh/guide/cli/d1) 或者 [用户界面更新 d1](/zh/guide/ui/d1) 18 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/admin.md: -------------------------------------------------------------------------------- 1 | # Admin 控制台 2 | 3 | > [!NOTE] 4 | > 需要配置 `ADMIN_PASSWORDS` 或者 `ADMIN_USER_ROLE` 才可以访问 admin 控制台 5 | > admin 角色配置, 如果用户角色等于 ADMIN_USER_ROLE 则可以访问 admin 控制台 6 | 7 | 部署前端应用之后,点击 左上角 logo 5 次 或者访问 `/admin` 路径即可进入管理控制台。 8 | 9 | 需要在后端配置 `ADMIN_PASSWORDS` 或者当前用户角色为 `ADMIN_USER_ROLE`, 则不允许访问控制台。 10 | 11 | ![admin](/feature/admin.png) 12 | 13 | ## 如果你的网站只可私人访问,可通过此禁用检查 14 | 15 | `DISABLE_ADMIN_PASSWORD_CHECK = true` 16 | -------------------------------------------------------------------------------- /smtp_proxy_server/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | smtp_proxy_server: 3 | image: ghcr.io/dreamhunter2333/cloudflare_temp_email/smtp_proxy_server:latest 4 | # build: 5 | # context: . 6 | # dockerfile: dockerfile 7 | container_name: "smtp_proxy_server" 8 | ports: 9 | - "8025:8025" 10 | - "11143:11143" 11 | environment: 12 | - proxy_url=https://temp-email-api.xxx.xxx 13 | - port=8025 14 | - imap_port=11143 15 | -------------------------------------------------------------------------------- /frontend/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export type UserOauth2Settings = { 2 | name: string; 3 | clientID: string; 4 | clientSecret: string; 5 | authorizationURL: string; 6 | accessTokenURL: string; 7 | accessTokenFormat?: string; 8 | userInfoURL: string; 9 | redirectURL: string; 10 | logoutURL?: string; 11 | userEmailKey: string; 12 | scope: string; 13 | enableMailAllowList?: boolean | undefined; 14 | mailAllowList?: string[] | undefined; 15 | } 16 | -------------------------------------------------------------------------------- /pages/functions/_middleware.js: -------------------------------------------------------------------------------- 1 | const API_PATHS = [ 2 | "/api/", 3 | "/open_api/", 4 | "/user_api/", 5 | "/admin/", 6 | "/telegram/", 7 | "/external/", 8 | ]; 9 | 10 | export async function onRequest(context) { 11 | const reqPath = new URL(context.request.url).pathname; 12 | if (API_PATHS.map(path => reqPath.startsWith(path)).some(Boolean)) { 13 | return context.env.BACKEND.fetch(context.request); 14 | } 15 | return await context.next(); 16 | } 17 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/cli/pre-requisite.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | ## Installing wrangler 4 | 5 | Install wrangler 6 | 7 | ```bash 8 | npm install wrangler -g 9 | ``` 10 | 11 | ## Clone the Project 12 | 13 | ```bash 14 | git clone https://github.com/dreamhunter2333/cloudflare_temp_email.git 15 | # Switch to the latest tag or the branch you want to deploy, you can also use the main branch directly 16 | # git checkout $(git describe --tags $(git rev-list --tags --max-count=1)) 17 | ``` 18 | -------------------------------------------------------------------------------- /mail-parser-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | web/ 16 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/subdomain.md: -------------------------------------------------------------------------------- 1 | # Configure Subdomain Email 2 | 3 | ::: warning Note 4 | Subdomain emails may not be able to send emails. It is recommended to use main domain emails for sending and subdomain emails only for receiving. 5 | 6 | Mail channel is no longer supported. The reference below is limited to the receiving part only. 7 | ::: 8 | 9 | Reference 10 | 11 | - [Configure Subdomain Email](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/164#issuecomment-2082612710) 12 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/star-history.md: -------------------------------------------------------------------------------- 1 | # Star History 2 | 3 | 4 | 5 | 6 | Star History Chart 7 | 8 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/star-history.md: -------------------------------------------------------------------------------- 1 | # Star History 2 | 3 | 4 | 5 | 6 | Star History Chart 7 | 8 | -------------------------------------------------------------------------------- /pages/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | .env.* 31 | *-dist/ 32 | components.d.ts 33 | .wrangler/ 34 | pnpm-lock.yaml 35 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | .env.* 31 | !.env.example 32 | !.env.pages 33 | *-dist/ 34 | components.d.ts 35 | -------------------------------------------------------------------------------- /worker/eslint.config.js: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | 5 | 6 | export default [ 7 | { 8 | languageOptions: { globals: globals.browser }, 9 | }, 10 | pluginJs.configs.recommended, 11 | ...tseslint.configs.recommended, 12 | { 13 | rules: { 14 | "@typescript-eslint/no-unused-vars": "off", 15 | "@typescript-eslint/no-explicit-any": "off", 16 | "@typescript-eslint/ban-ts-comment": "off", 17 | } 18 | } 19 | ]; 20 | -------------------------------------------------------------------------------- /smtp_proxy_server/config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pydantic_settings import BaseSettings 3 | 4 | logging.basicConfig( 5 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 6 | ) 7 | 8 | _logger = logging.getLogger(__name__) 9 | _logger.setLevel(logging.INFO) 10 | 11 | 12 | class Settings(BaseSettings): 13 | proxy_url: str = "http://localhost:8787" 14 | port: int = 8025 15 | imap_port: int = 11143 16 | basic_password: str = "" 17 | 18 | class Config: 19 | env_file = ".env" 20 | 21 | 22 | settings = Settings() 23 | -------------------------------------------------------------------------------- /pages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temp-email-pages", 3 | "version": "1.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "wrangler pages dev", 8 | "deploy": "wrangler pages deploy --branch production" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "wrangler": "^4.53.0" 15 | }, 16 | "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39" 17 | } 18 | -------------------------------------------------------------------------------- /worker/src/email/black_list.ts: -------------------------------------------------------------------------------- 1 | import { CONSTANTS } from "../constants"; 2 | 3 | export const isBlocked = async (from: string, env: Bindings): Promise => { 4 | if (env.BLACK_LIST && env.BLACK_LIST.split(",").some(word => from.includes(word))) { 5 | return true; 6 | } 7 | if (!env.KV) { 8 | return false; 9 | } 10 | const blockList = await env.KV.get(CONSTANTS.EMAIL_KV_BLACK_LIST, 'json') || []; 11 | if (blockList.some(word => from.includes(word))) { 12 | return true; 13 | } 14 | return false; 15 | } 16 | -------------------------------------------------------------------------------- /db/2024-08-10-patch.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS user_passkeys ( 2 | id INTEGER PRIMARY KEY, 3 | user_id INTEGER NOT NULL, 4 | passkey_name TEXT NOT NULL, 5 | passkey_id TEXT NOT NULL, 6 | passkey TEXT NOT NULL, 7 | counter INTEGER DEFAULT 0, 8 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 9 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP 10 | ); 11 | 12 | CREATE INDEX IF NOT EXISTS idx_user_passkeys_user_id ON user_passkeys(user_id); 13 | 14 | CREATE UNIQUE INDEX IF NOT EXISTS idx_user_passkeys_user_id_passkey_id ON user_passkeys(user_id, passkey_id); 15 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/email-routing.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Email Routing 2 | 3 | 1. In the CF console for the corresponding domain under `Email Routing`, configure the `Email DNS records`. If there are multiple domains, you need to configure `Email DNS records` for each domain. 4 | 5 | 2. Before binding an email address to your Worker, you need to enable email routing and have at least one verified email address (destination address). 6 | 7 | 3. Configure the `Catch-all address` in the routing rules of each domain's `Email Routing` to send to `worker`. 8 | 9 | ![email](/readme_assets/email.png) 10 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/what-is-temp-mail.md: -------------------------------------------------------------------------------- 1 | # Introduction to Temporary Email 2 | 3 | ## What is Temporary Email 4 | 5 | A temporary email, also known as disposable email or temporary email address, is a virtual mailbox used for temporarily receiving emails. Unlike regular mailboxes, temporary emails are designed to provide an anonymous and temporary email receiving solution. 6 | 7 | Temporary emails are often provided by websites or online service providers. Users can use temporary email addresses when they need to register or receive verification emails, without exposing their real email address. The benefit of this is to protect personal privacy. 8 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/user-oauth2.md: -------------------------------------------------------------------------------- 1 | # OAuth2 第三方登录 2 | 3 | > [!WARNING] 注意 4 | > 第三方登录会自动使用用户邮箱注册账号(邮箱相同将视为同一账号) 5 | > 6 | > 此账号和注册的账号相同, 也可以通过忘记密码设置密码 7 | 8 | ## 在第三方平台注册 OAuth2 9 | 10 | ### GitHub 11 | 12 | - 请先创建一个 OAuth App,然后获取 `Client ID` 和 `Client Secret` 13 | 14 | 参考 [Creating an OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) 15 | 16 | ### Authentik 17 | 18 | - [Authentik OAuth2 Provider](https://docs.goauthentik.io/docs/providers/oauth2/) 19 | 20 | ## Admin 后台配置 OAuth2 21 | 22 | ![oauth2](/feature/oauth2.png) 23 | 24 | ## 测试用户登录页面 25 | 26 | ![oauth2 login](/feature/oauth2-login.png) 27 | -------------------------------------------------------------------------------- /worker/src/admin_api/send_mail.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { sendMail } from "../mails_api/send_mail_api"; 3 | 4 | export const sendMailbyAdmin = async (c: Context) => { 5 | const { 6 | from_name, from_mail, 7 | to_mail, to_name, 8 | subject, content, is_html 9 | } = await c.req.json(); 10 | await sendMail(c, from_mail, { 11 | from_name: from_name, 12 | to_name: to_name, 13 | to_mail: to_mail, 14 | subject: subject, 15 | content: content, 16 | is_html: is_html, 17 | }, { 18 | isAdmin: true 19 | }) 20 | return c.json({ status: "ok" }); 21 | } 22 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/google-ads.md: -------------------------------------------------------------------------------- 1 | # 给网页增加 Google Ads 2 | 3 | ## 命令行部署 4 | 5 | 修改 `.env.prod` 文件 6 | 7 | 增加下列两个变量, 具体的值请参考 [Google AdSense](https://www.google.com/adsense/start/) 的说明 8 | 9 | ```txt 10 | VITE_GOOGLE_AD_CLIENT=ca-pub-123456 11 | VITE_GOOGLE_AD_SLOT=123456 12 | ``` 13 | 14 | 然后执行下列命令, 重新部署 pages 即可. 15 | 16 | ```bash 17 | pnpm build --emptyOutDir 18 | # 第一次部署会提示创建项目, production 分支请填写 production 19 | pnpm run deploy 20 | ``` 21 | 22 | ## GitHub Action 部署 23 | 24 | 修改 `FRONTEND_ENV`, 增加下列两个变量, 具体的值请参考 [Google AdSense](https://www.google.com/adsense/start/) 的说明, 重新部署 pages 即可. 25 | 26 | ```txt 27 | VITE_GOOGLE_AD_CLIENT=ca-pub-123456 28 | VITE_GOOGLE_AD_SLOT=123456 29 | ``` 30 | -------------------------------------------------------------------------------- /frontend/src/views/admin/MailsUnknow.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /frontend/src/views/common/AdminContact.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/actions/d1.md: -------------------------------------------------------------------------------- 1 | # Initialize/Update D1 Database 2 | 3 | ## Create Database 4 | 5 | Open the Cloudflare console, select `Workers & Pages` -> `D1` -> `Create Database`, and click to create the database 6 | 7 | ![d1](/ui_install/d1.png) 8 | 9 | After creation, you can see the D1 database in the Cloudflare console and obtain the database `name` and `database ID` 10 | 11 | ## Initialize Database 12 | 13 | After deployment is complete, go to the admin page's `Quick Setup` -> `Database` section and click the `Initialize Database` button to initialize the database 14 | 15 | ## Update Database Schema 16 | 17 | Refer to [Update D1 via Command Line](/en/guide/cli/d1) or [Update D1 via UI](/en/guide/ui/d1) 18 | -------------------------------------------------------------------------------- /mail-parser-wasm/worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mail-parser-wasm-worker", 3 | "description": "A simple mail parser for worker", 4 | "homepage": "https://github.com/dreamhunter2333/cloudflare_temp_email/tree/main/mail-parser-wasm", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/dreamhunter2333/cloudflare_temp_email", 8 | "directory": "mail-parser-wasm" 9 | }, 10 | "version": "0.2.1", 11 | "license": "MIT", 12 | "files": [ 13 | "mail_parser_wasm_bg.wasm", 14 | "mail_parser_wasm.js", 15 | "mail_parser_wasm.d.ts", 16 | "index.js", 17 | "index.d.ts" 18 | ], 19 | "module": "index.js", 20 | "types": "index.d.ts", 21 | "sideEffects": [ 22 | "./snippets/*" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/actions/auto-update.md: -------------------------------------------------------------------------------- 1 | # How to Configure Auto-Update for GitHub Actions Deployment 2 | 3 | ::: warning Notice 4 | If you encounter any issues, please report them via `GitHub Issues`. Thank you. 5 | Auto-update will not execute SQL files for the D1 database. When the database schema changes, you need to execute them manually. 6 | ::: 7 | 8 | 1. Open the `Actions` page of the repository, find `Upstream Sync`, and click `enable workflow` to enable the `workflow` 9 | 2. If `Upstream Sync` fails, go to the repository homepage and click `Sync` to synchronize manually 10 | 3. You can customize the update interval by modifying the `schedule` configuration in `Upstream Sync`, refer to [cron expressions](https://crontab.guru/) 11 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/admin.md: -------------------------------------------------------------------------------- 1 | # Admin Console 2 | 3 | > [!NOTE] 4 | > You need to configure `ADMIN_PASSWORDS` or `ADMIN_USER_ROLE` to access the admin console 5 | > Admin role configuration: if the user role equals ADMIN_USER_ROLE, they can access the admin console 6 | 7 | After deploying the frontend application, click the upper-left logo 5 times or visit the `/admin` path to enter the management console. 8 | 9 | You need to configure `ADMIN_PASSWORDS` in the backend or ensure the current user role is `ADMIN_USER_ROLE`, otherwise access to the console will be denied. 10 | 11 | ![admin](/feature/admin.png) 12 | 13 | ## If your website is for private access only, you can disable this check 14 | 15 | `DISABLE_ADMIN_PASSWORD_CHECK = true` 16 | -------------------------------------------------------------------------------- /db/2024-05-08-patch.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS users ( 2 | id INTEGER PRIMARY KEY, 3 | user_email TEXT UNIQUE NOT NULL, 4 | password TEXT NOT NULL, 5 | user_info TEXT, 6 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 7 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP 8 | ); 9 | 10 | CREATE INDEX IF NOT EXISTS idx_users_user_email ON users(user_email); 11 | 12 | CREATE TABLE IF NOT EXISTS users_address ( 13 | id INTEGER PRIMARY KEY, 14 | user_id INTEGER, 15 | address_id INTEGER UNIQUE, 16 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP 17 | ); 18 | 19 | CREATE INDEX IF NOT EXISTS idx_users_address_user_id ON users_address(user_id); 20 | 21 | CREATE INDEX IF NOT EXISTS idx_users_address_address_id ON users_address(address_id); 22 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/cli/d1.md: -------------------------------------------------------------------------------- 1 | # 初始化/更新 D1 数据库 2 | 3 | 第一次执行登录 wrangler 命令时,会提示登录, 按提示操作即可 4 | 5 | ## 初始化数据库 6 | 7 | ```bash 8 | cd worker 9 | cp wrangler.toml.template wrangler.toml 10 | # 创建 D1 并执行 schema.sql 11 | wrangler d1 create dev 12 | wrangler d1 execute dev --file=../db/schema.sql --remote 13 | ``` 14 | 15 | 创建完成后,我们在 cloudflare 的控制台可以看到 D1 数据库 16 | 17 | ![D1](/readme_assets/d1.png) 18 | 19 | ## 更新数据库 schema 20 | 21 | `schema` 更新,请确认你之前部署的版本, 22 | 查看 [更新日志](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md) 23 | 24 | 找到需要执行的 `patch` 文件, 执行, 例如: 25 | 26 | ```bash 27 | cd worker 28 | wrangler d1 execute dev --file=../db/2024-01-13-patch.sql --remote 29 | wrangler d1 execute dev --file=../db/2024-04-03-patch.sql --remote 30 | ``` 31 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # cloudflare_temp_email 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 8 | 9 | ## Customize configuration 10 | 11 | See [Vite Configuration Reference](https://vitejs.dev/config/). 12 | 13 | ## Project Setup 14 | 15 | ```sh 16 | npm install 17 | ``` 18 | 19 | ### Compile and Hot-Reload for Development 20 | 21 | ```sh 22 | npm run dev 23 | ``` 24 | 25 | ### Compile and Minify for Production 26 | 27 | ```sh 28 | npm run build 29 | ``` 30 | -------------------------------------------------------------------------------- /smtp_proxy_server/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import multiprocessing 3 | 4 | from smtp_server import start_smtp_server 5 | from imap_server import start_imap_server 6 | from config import settings 7 | 8 | _logger = logging.getLogger(__name__) 9 | _logger.setLevel(logging.INFO) 10 | 11 | if __name__ == '__main__': 12 | _logger.info(f"Starting server settings[{settings}]") 13 | process_list = [ 14 | multiprocessing.Process(target=start_smtp_server, args=()), 15 | multiprocessing.Process(target=start_imap_server, args=()), 16 | ] 17 | try: 18 | for p in process_list: 19 | p.start() 20 | for p in process_list: 21 | p.join() 22 | except KeyboardInterrupt: 23 | for p in process_list: 24 | p.terminate() 25 | -------------------------------------------------------------------------------- /vitepress-docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temp-mail-docs", 3 | "private": true, 4 | "version": "1.1.0", 5 | "type": "module", 6 | "devDependencies": { 7 | "@types/node": "^24.10.1", 8 | "vitepress": "^1.6.4", 9 | "wrangler": "^4.53.0" 10 | }, 11 | "scripts": { 12 | "dev": "vitepress dev docs", 13 | "build": "vitepress build docs", 14 | "preview": "vitepress preview docs", 15 | "deploy": "npm run build && wrangler pages deploy ./docs/.vitepress/dist --project-name=temp-mail-docs --branch production" 16 | }, 17 | "dependencies": { 18 | "jszip": "^3.10.1" 19 | }, 20 | "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39" 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/sync.yaml: -------------------------------------------------------------------------------- 1 | name: Upstream Sync 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * 1" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | sync_latest_from_upstream: 10 | name: Sync latest commits from upstream repo 11 | runs-on: ubuntu-latest 12 | if: ${{ github.event.repository.fork }} 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Sync upstream changes 18 | id: sync 19 | uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 20 | with: 21 | upstream_sync_repo: dreamhunter2333/cloudflare_temp_email 22 | upstream_sync_branch: main 23 | target_sync_branch: main 24 | target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set 25 | test_mode: false 26 | -------------------------------------------------------------------------------- /worker/src/admin_api/webhook_settings.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { CONSTANTS } from "../constants"; 3 | import { AdminWebhookSettings } from "../models"; 4 | 5 | async function getWebhookSettings(c: Context): Promise { 6 | const settings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json"); 7 | return c.json(settings || new AdminWebhookSettings(false, [])); 8 | } 9 | 10 | async function saveWebhookSettings(c: Context): Promise { 11 | const settings = await c.req.json(); 12 | await c.env.KV.put(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, JSON.stringify(settings)); 13 | return c.json({ success: true }) 14 | } 15 | 16 | export default { 17 | getWebhookSettings, 18 | saveWebhookSettings, 19 | } 20 | -------------------------------------------------------------------------------- /worker/src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import { LocaleMessages } from "./type"; 2 | import zh from "./zh"; 3 | import en from "./en"; 4 | import { Context } from "hono"; 5 | 6 | export default { 7 | getMessages: ( 8 | locale: string | null | undefined 9 | ): LocaleMessages => { 10 | // multi-language support 11 | if (locale === "en") return en; 12 | if (locale === "zh") return zh; 13 | 14 | // fallback language 15 | return en; 16 | }, 17 | getMessagesbyContext: ( 18 | c: Context 19 | ): LocaleMessages => { 20 | const locale = c.get("lang") || c.env.DEFAULT_LANG; 21 | // multi-language support 22 | if (locale === "en") return en; 23 | if (locale === "zh") return zh; 24 | 25 | // fallback language 26 | return en; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/views/index/Webhook.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/ui/d1.md: -------------------------------------------------------------------------------- 1 | # 初始化/更新 D1 数据库 2 | 3 | ## 创建数据库 4 | 5 | 打开 cloudflare 控制台,选择 `Storage & Databases` -> `D1 SQL Database` -> `Create Database`,点击创建数据库 6 | 7 | ![d1](/ui_install/d1.png) 8 | 9 | 创建完成后,我们在 cloudflare 的控制台可以看到 D1 数据库 10 | 11 | ## 初始化数据库 12 | 13 | ::: warning 注意 14 | 你也可以跳过初始化数据库,在部署完成后,在 admin 页面的 `快速设置` -> `数据库` 中,点击 `初始化数据库` 按钮来初始化数据库 15 | ::: 16 | 17 | 打开 `Console` 标签页,输入仓库中 [db/schema.sql](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/db/schema.sql) 文件的内容,点击 `Execute` 执行 18 | 19 | ![d1](/ui_install/d1-exec.png) 20 | 21 | ## 更新数据库 schema 22 | 23 | `schema` 更新,请确认你之前部署的版本, 24 | 25 | 查看 [更新日志](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md) 26 | 27 | 找到需要执行的 `patch` 文件, 执行, 例如: `db/2024-01-13-patch.sql` 28 | 29 | 打开 `Console` 标签页,输入 `patch` 文件的内容,点击 `Execute` 执行 30 | 31 | ![d1](/ui_install/d1-exec.png) 32 | -------------------------------------------------------------------------------- /frontend/src/views/admin/MailWebhook.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /.github/workflows/pr_agent.yml: -------------------------------------------------------------------------------- 1 | name: Codium PR Agent 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, ready_for_review] 6 | jobs: 7 | pr_agent_job: 8 | if: ${{ github.event.sender.type != 'Bot' }} 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | name: Run pr agent on every pull request, respond to user comments 14 | steps: 15 | - name: PR Agent action step 16 | id: pragent 17 | uses: docker://codiumai/pr-agent:0.29-github_action 18 | env: 19 | PR_REVIEWER.REQUIRE_TESTS_REVIEW: "false" 20 | OPENAI_KEY: ${{ secrets.OPENAI_KEY }} 21 | OPENAI_API_BASE: ${{ secrets.OPENAI_API_BASE }} 22 | CONFIG.MODEL: "gpt-4o" 23 | CONFIG.MODEL_TURBO: "gpt-4o" 24 | OPENAI.API_BASE: ${{ secrets.OPENAI_API_BASE }} 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/google-ads.md: -------------------------------------------------------------------------------- 1 | # Adding Google Ads to Your Website 2 | 3 | ## Command Line Deployment 4 | 5 | Modify the `.env.prod` file 6 | 7 | Add the following two variables, refer to [Google AdSense](https://www.google.com/adsense/start/) for specific values 8 | 9 | ```txt 10 | VITE_GOOGLE_AD_CLIENT=ca-pub-123456 11 | VITE_GOOGLE_AD_SLOT=123456 12 | ``` 13 | 14 | Then execute the following commands to redeploy pages. 15 | 16 | ```bash 17 | pnpm build --emptyOutDir 18 | # For first deployment, you'll be prompted to create a project, fill in production for the production branch 19 | pnpm run deploy 20 | ``` 21 | 22 | ## GitHub Action Deployment 23 | 24 | Modify `FRONTEND_ENV`, add the following two variables, refer to [Google AdSense](https://www.google.com/adsense/start/) for specific values, then redeploy pages. 25 | 26 | ```txt 27 | VITE_GOOGLE_AD_CLIENT=ca-pub-123456 28 | VITE_GOOGLE_AD_SLOT=123456 29 | ``` 30 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/user-oauth2.md: -------------------------------------------------------------------------------- 1 | # OAuth2 Third-Party Login 2 | 3 | > [!WARNING] Note 4 | > Third-party login will automatically register an account using the user's email (emails with the same address will be considered the same account) 5 | > 6 | > This account is the same as a registered account and can also set a password through the forgot password feature 7 | 8 | ## Register OAuth2 on Third-Party Platforms 9 | 10 | ### GitHub 11 | 12 | - Please first create an OAuth App, then obtain the `Client ID` and `Client Secret` 13 | 14 | Reference: [Creating an OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) 15 | 16 | ### Authentik 17 | 18 | - [Authentik OAuth2 Provider](https://docs.goauthentik.io/docs/providers/oauth2/) 19 | 20 | ## Configure OAuth2 in Admin Backend 21 | 22 | ![oauth2](/feature/oauth2.png) 23 | 24 | ## Test User Login Page 25 | 26 | ![oauth2 login](/feature/oauth2-login.png) 27 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/s3-attachment.md: -------------------------------------------------------------------------------- 1 | # 配置 S3 附件 2 | 3 | ## 配置 4 | 5 | > [!NOTE] 6 | > 如果不需要 S3 附件, 可跳过此步骤 7 | 8 | 在 Cloudflare 创建一个 R2 bucket, 你也可以使用其他的 S3 服务(如有 bug 请提 issue) 9 | 10 | 参考: [配置 Cloudflare R2 的 cors](https://developers.cloudflare.com/r2/buckets/cors/#add-cors-policies-from-the-dashboard) 11 | 12 | 参考 [Cloudflare R2 s3 toke](https://developers.cloudflare.com/r2/api/s3/tokens/) 创建 token, 拿到 `ENDPOINT`, `Access Key ID` 和 `Secret Access Key`,然后执行下面的命令添加到 secrets 中 13 | 14 | > [!NOTE] 15 | > 你也可以在 Cloudflare worker 的 UI 界面中添加 `secrets` 16 | 17 | ```bash 18 | cd worker 19 | pnpm wrangler secret put S3_ENDPOINT 20 | pnpm wrangler secret put S3_ACCESS_KEY_ID 21 | pnpm wrangler secret put S3_SECRET_ACCESS_KEY 22 | # 请注意这里的 bucket 是你的 bucket 名称 23 | pnpm wrangler secret put S3_BUCKET 24 | ``` 25 | 26 | ## 使用 27 | 28 | 保存附件 29 | 30 | ![S3 save](/feature/s3-save.png) 31 | 32 | 下载附件 33 | 34 | ![S3 download](/public/feature/s3-download.png) 35 | -------------------------------------------------------------------------------- /worker/src/admin_api/ai_extract_settings.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { CONSTANTS } from "../constants"; 3 | import { getJsonSetting, saveSetting } from "../utils"; 4 | 5 | export type AiExtractSettings = { 6 | enableAllowList: boolean; 7 | allowList: string[]; 8 | } 9 | 10 | async function getAiExtractSettings(c: Context): Promise { 11 | const settings = await getJsonSetting(c, CONSTANTS.AI_EXTRACT_SETTINGS_KEY) || { 12 | enableAllowList: false, 13 | allowList: [] 14 | }; 15 | return c.json(settings); 16 | } 17 | 18 | async function saveAiExtractSettings(c: Context): Promise { 19 | const settings = await c.req.json(); 20 | await saveSetting(c, CONSTANTS.AI_EXTRACT_SETTINGS_KEY, JSON.stringify(settings)); 21 | return c.json({ success: true }) 22 | } 23 | 24 | export default { 25 | getAiExtractSettings, 26 | saveAiExtractSettings, 27 | } 28 | -------------------------------------------------------------------------------- /vitepress-docs/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "临时邮箱文档" 7 | tagline: "搭建 CloudFlare 免费收发 临时域名邮箱" 8 | actions: 9 | - theme: brand 10 | text: 立即试用 11 | link: https://mail.awsl.uk/ 12 | - theme: alt 13 | text: 命令行部署 14 | link: /zh/guide/quick-start 15 | - theme: alt 16 | text: 通过用户界面部署 17 | link: /zh/guide/quick-start 18 | - theme: alt 19 | text: 通过 Github Actions 部署 20 | link: /zh/guide/quick-start 21 | 22 | features: 23 | - title: 仅需域名即可私有部署, 免费托管在 CloudFlare,无需服务器 24 | details: 支持 password 登录邮箱, 用户注册,使用访问密码可作为私人站点,支持附件功能。 25 | - title: 使用 rust wasm 解析邮件 26 | details: 使用 rust wasm 解析邮件,支持邮件各种RFC标准,支持附件, 速度极快 27 | - title: 支持 Telegram Bot 和 Webhook 28 | details: 邮件可转发到 Telegram 或者 webhook, Telegram Bot 支持绑定邮箱,查看邮件, Telegram 小程序 29 | - title: 支持发送邮件(UI/API/SMTP) 30 | details: 支持通过域名邮箱发送 txt 或者 html 邮件,支持 DKIM 签名, UI/API/SMTP 发送邮件 31 | --- 32 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Temp Email 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/cli/d1.md: -------------------------------------------------------------------------------- 1 | # Initialize/Update D1 Database 2 | 3 | When executing the wrangler login command for the first time, you will be prompted to log in. Follow the prompts to complete the login process. 4 | 5 | ## Initialize Database 6 | 7 | ```bash 8 | cd worker 9 | cp wrangler.toml.template wrangler.toml 10 | # Create D1 and execute schema.sql 11 | wrangler d1 create dev 12 | wrangler d1 execute dev --file=../db/schema.sql --remote 13 | ``` 14 | 15 | After creation, you can see the D1 database in the Cloudflare console. 16 | 17 | ![D1](/readme_assets/d1.png) 18 | 19 | ## Update Database Schema 20 | 21 | For `schema` updates, please confirm your previously deployed version. 22 | Check the [Changelog](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md) 23 | 24 | Find the `patch` file you need to execute and run it, for example: 25 | 26 | ```bash 27 | cd worker 28 | wrangler d1 execute dev --file=../db/2024-01-13-patch.sql --remote 29 | wrangler d1 execute dev --file=../db/2024-04-03-patch.sql --remote 30 | ``` 31 | -------------------------------------------------------------------------------- /frontend/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const hashPassword = async (password: string) => { 2 | // user crypto to hash password 3 | const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password)); 4 | const hashArray = Array.from(new Uint8Array(digest)); 5 | return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); 6 | } 7 | 8 | export const getRouterPathWithLang = (path: string, lang: string) => { 9 | if (!lang || lang === 'zh') { 10 | return path; 11 | } 12 | return `/${lang}${path}`; 13 | } 14 | 15 | export const utcToLocalDate = (utcDate: string, useUTCDate: boolean) => { 16 | const utcDateString = `${utcDate} UTC`; 17 | if (useUTCDate) { 18 | return utcDateString; 19 | } 20 | try { 21 | const date = new Date(utcDateString); 22 | // if invalid date string 23 | if (isNaN(date.getTime())) return utcDateString; 24 | 25 | return date.toLocaleString(); 26 | } catch (e) { 27 | console.error(e); 28 | } 29 | return utcDateString; 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/views/user/BindAddress.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 37 | 38 | 46 | -------------------------------------------------------------------------------- /frontend/src/views/Footer.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 35 | 36 | 37 | 43 | -------------------------------------------------------------------------------- /frontend/src/views/admin/WorkerConfig.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 34 | 35 | 43 | -------------------------------------------------------------------------------- /frontend/src/utils/fingerprint.ts: -------------------------------------------------------------------------------- 1 | import FingerprintJS from '@fingerprintjs/fingerprintjs'; 2 | import { useGlobalState } from '../store'; 3 | 4 | const { browserFingerprint } = useGlobalState(); 5 | 6 | /** 7 | * Get browser fingerprint 8 | * Uses cached value from global state if available to avoid unnecessary computation 9 | * @returns Fingerprint visitor ID, or 'ERROR' if failed 10 | */ 11 | export const getFingerprint = async (): Promise => { 12 | // Return cached fingerprint if available 13 | if (browserFingerprint.value) { 14 | return browserFingerprint.value; 15 | } 16 | 17 | try { 18 | const fp = await FingerprintJS.load(); 19 | const result = await fp.get(); 20 | browserFingerprint.value = result.visitorId; 21 | return browserFingerprint.value; 22 | } catch (error) { 23 | console.error('Failed to get fingerprint:', error); 24 | // Return special error value to prevent blocking requests 25 | const errorValue = 'ERROR'; 26 | browserFingerprint.value = errorValue; 27 | return errorValue; 28 | } 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /mail-parser-wasm/README.md: -------------------------------------------------------------------------------- 1 | # mail-parser-wasm web and cf worker 2 | 3 | ## [mail-parser-wasm](https://www.npmjs.com/package/mail-parser-wasm) 4 | 5 | ### mail-parser-wasm usage 6 | 7 | ```bash 8 | pnpm add mail-parser-wasm 9 | ``` 10 | 11 | ```js 12 | import { parse_message } from 'mail-parser-wasm' 13 | 14 | const parsedEmail = parse_message(rawEmail); 15 | ``` 16 | 17 | ### mail-parser-wasm build 18 | 19 | ```bash 20 | wasm-pack build --release 21 | wasm-pack publish 22 | ``` 23 | 24 | ## [mail-parser-wasm-worker](https://www.npmjs.com/package/mail-parser-wasm-worker) 25 | 26 | ### mail-parser-wasm-worker usage 27 | 28 | ```bash 29 | pnpm add mail-parser-wasm-worker 30 | ``` 31 | 32 | ```js 33 | import { parse_message_wrapper } from 'mail-parser-wasm-worker' 34 | 35 | const parsedEmail = parse_message_wrapper(rawEmail); 36 | ``` 37 | 38 | ### mail-parser-wasm-worker build 39 | 40 | ```bash 41 | wasm-pack build --out-dir web --target web --release 42 | find web/ -type f ! -name '*.json' ! -name '.gitignore' -exec cp {} worker/ \; 43 | # modify worker/package.json version or whatever 44 | pnpm publish worker --no-git-checks 45 | ``` 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dream Hunter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/cli/pages.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Pages 前端 2 | 3 | > [!warning] 注意 4 | > 下面几种方式选择一种即可 5 | 6 | ## 部署带有前端资源的 Worker 7 | 8 | 参考 [部署 Worker](/zh/guide/cli/worker#部署带有前端页面的-worker-可选) 9 | 10 | ## 前后端分离部署 11 | 12 | 第一次部署会提示创建项目, `production` 分支请填写 `production` 13 | 14 | ```bash 15 | cd frontend 16 | pnpm install 17 | cp .env.example .env.prod 18 | ``` 19 | 20 | 修改 `.env.prod` 文件 21 | 22 | 将 `VITE_API_BASE` 修改为上一步创建的 `worker` 的 `url`, 不要在末尾加 `/` 23 | 24 | 例如: `VITE_API_BASE=https://xxx.xxx.workers.dev` 25 | 26 | ```bash 27 | pnpm build --emptyOutDir 28 | # 第一次部署会提示创建项目, production 分支请填写 production 29 | pnpm run deploy 30 | ``` 31 | 32 | 部署完成之后你可以在 Cloudflare 控制台看到你的项目, 可以为 `pages` 配置自定义域名 33 | 34 | ![pages](/readme_assets/pages.png) 35 | 36 | ## 通过 page functions 转发后端请求 37 | 38 | 从 page functions 转发请求到 worker 后端, 可以获取更快的响应速度 39 | 40 | 第一次部署会提示创建项目, `production` 分支请填写 `production` 41 | 42 | 如果你的 worker 后端 名称不为 `cloudflare_temp_email` 请修改 `pages/wrangler.toml` 43 | 44 | ```bash 45 | cd frontend 46 | pnpm install 47 | # 如果你要启用 Cloudflare Zero Trust, 需要使用 pnpm build:pages:nopwa 来禁用缓存 48 | pnpm build:pages 49 | cd ../pages 50 | pnpm run deploy 51 | ``` 52 | -------------------------------------------------------------------------------- /worker/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const CONSTANTS = { 2 | VERSION: 'v' + '1.1.0', 3 | 4 | // DB Version 5 | DB_VERSION_KEY: 'db_version', 6 | DB_VERSION: "v0.0.4", 7 | 8 | // DB settings 9 | ADDRESS_BLOCK_LIST_KEY: 'address_block_list', 10 | SEND_BLOCK_LIST_KEY: 'send_block_list', 11 | AUTO_CLEANUP_KEY: 'auto_cleanup', 12 | USER_SETTINGS_KEY: 'user_settings', 13 | OAUTH2_SETTINGS_KEY: 'oauth2_settings', 14 | VERIFIED_ADDRESS_LIST_KEY: 'verified_address_list', 15 | NO_LIMIT_SEND_ADDRESS_LIST_KEY: 'no_limit_send_address_list', 16 | EMAIL_RULE_SETTINGS_KEY: 'email_rule_settings', 17 | ROLE_ADDRESS_CONFIG_KEY: 'role_address_config', 18 | IP_BLACKLIST_SETTINGS_KEY: 'ip_blacklist_settings', 19 | AI_EXTRACT_SETTINGS_KEY: 'ai_extract_settings', 20 | 21 | // KV 22 | TG_KV_PREFIX: "temp-mail-telegram", 23 | TG_KV_SETTINGS_KEY: "temp-mail-telegram-settings", 24 | WEBHOOK_KV_SETTINGS_KEY: "temp-mail-webhook-settings", 25 | WEBHOOK_KV_USER_SETTINGS_KEY: "temp-mail-webhook-user-settings", 26 | EMAIL_KV_BLACK_LIST: "temp-mail-email-black-list", 27 | WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY: "temp-mail-webhook-admin-mail-settings", 28 | } 29 | -------------------------------------------------------------------------------- /worker/src/admin_api/cleanup_api.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'hono'; 2 | 3 | import { cleanup } from '../common'; 4 | import { CONSTANTS } from '../constants'; 5 | import { getJsonSetting, saveSetting } from '../utils'; 6 | import { CleanupSettings } from '../models'; 7 | 8 | export default { 9 | cleanup: async (c: Context) => { 10 | const { cleanType, cleanDays } = await c.req.json(); 11 | try { 12 | await cleanup(c, cleanType, cleanDays); 13 | } catch (error) { 14 | console.error(error); 15 | return c.text(`Failed to cleanup ${(error as Error).message}`, 500) 16 | } 17 | return c.json({ success: true }) 18 | }, 19 | getCleanup: async (c: Context) => { 20 | const cleanupSetting = await getJsonSetting(c, CONSTANTS.AUTO_CLEANUP_KEY); 21 | return c.json(cleanupSetting) 22 | }, 23 | saveCleanup: async (c: Context) => { 24 | const cleanupSetting = await c.req.json(); 25 | await saveSetting(c, CONSTANTS.AUTO_CLEANUP_KEY, JSON.stringify(cleanupSetting)); 26 | return c.json({ success: true }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/s3-attachment.md: -------------------------------------------------------------------------------- 1 | # Configure S3 Attachments 2 | 3 | ## Configuration 4 | 5 | > [!NOTE] 6 | > If you don't need S3 attachments, you can skip this step 7 | 8 | Create an R2 bucket in Cloudflare. You can also use other S3 services (please submit an issue if you encounter bugs). 9 | 10 | Reference: [Configure Cloudflare R2 cors](https://developers.cloudflare.com/r2/buckets/cors/#add-cors-policies-from-the-dashboard) 11 | 12 | Reference: [Cloudflare R2 s3 token](https://developers.cloudflare.com/r2/api/s3/tokens/) to create a token, obtain `ENDPOINT`, `Access Key ID` and `Secret Access Key`, then execute the following commands to add them to secrets 13 | 14 | > [!NOTE] 15 | > You can also add `secrets` in the Cloudflare worker UI interface 16 | 17 | ```bash 18 | cd worker 19 | pnpm wrangler secret put S3_ENDPOINT 20 | pnpm wrangler secret put S3_ACCESS_KEY_ID 21 | pnpm wrangler secret put S3_SECRET_ACCESS_KEY 22 | # Note: Replace bucket with your bucket name 23 | pnpm wrangler secret put S3_BUCKET 24 | ``` 25 | 26 | ## Usage 27 | 28 | Save attachment 29 | 30 | ![S3 save](/feature/s3-save.png) 31 | 32 | Download attachment 33 | 34 | ![S3 download](/public/feature/s3-download.png) 35 | -------------------------------------------------------------------------------- /.github/workflows/frontend_pagefunction_deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Frontend with page function 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: write 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Install Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20 19 | 20 | - uses: pnpm/action-setup@v3 21 | name: Install pnpm 22 | id: pnpm-install 23 | with: 24 | version: 8 25 | run_install: false 26 | 27 | - name: Deploy Frontend for ${{ github.ref_name }} 28 | run: | 29 | cd frontend/ 30 | pnpm install --no-frozen-lockfile 31 | pnpm build:pages 32 | cd ../pages/ 33 | echo '${{ secrets.PAGE_TOML }}' > wrangler.toml 34 | pnpm install --no-frozen-lockfile 35 | pnpm run deploy 36 | echo "Deploying prodcution for ${{ github.ref_name }}" 37 | env: 38 | CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 39 | CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/webhook.md: -------------------------------------------------------------------------------- 1 | # 配置 webhook 2 | 3 | > [!NOTE] 4 | > 如果要使用 webhook,请先绑定 `KV` 并且 `worker` 变量配置 `ENABLE_WEBHOOK = true` 5 | > 6 | > 如果你想 webhook 的解析邮件能力更强,参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker) 7 | 8 | ## 前提条件 9 | 10 | 你需要自建一个 `webhook 服务` 或者 使用 `第三方平台`,这个服务需要能够接收 `POST` 请求,并且能够解析 `json` 数据。 11 | 12 | 本项目使用了 [songquanpeng/message-pusher](https://github.com/songquanpeng/message-pusher) 示例作为 webhook 服务。 13 | 14 | - 可以使用 [msgpusher.com](https://msgpusher.com) 提供的服务 15 | - 也可以自建 `message-pusher` 服务,参考 [songquanpeng/message-pusher](https://github.com/songquanpeng/message-pusher) 16 | 17 | ## admin 配置全局 webhook 18 | 19 | ![telegram](/feature/admin-mail-webhook.png) 20 | 21 | ## admin 允许邮箱使用 webhook 22 | 23 | ![telegram](/feature/admin-webhook-settings.png) 24 | 25 | ## 某个邮箱配置 webhook 26 | 27 | ![telegram](/feature/address-webhook.png) 28 | 29 | ## webhook 数据格式 30 | 31 | 要获取 url 需要配置 worker 的 `FRONTEND_URL` 为你的前端地址,或者你可以通过 `id` 自己拼接 url = `${FRONTEND_URL}?mail_id=${id}` 32 | 33 | ```json 34 | { 35 | "id": "${id}", 36 | "url": "${url}", 37 | "from": "${from}", 38 | "to": "${to}", 39 | "subject": "${subject}", 40 | "raw": "${raw}", 41 | "parsedText": "${parsedText}", 42 | "parsedHtml": "${parsedHtml}", 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/quick-start.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | ## 开始之前 4 | 5 | 需要 `良好的网络环境` 和 `cloudflare 账号`, 打开 [cloudflare控制台](https://dash.cloudflare.com/) 6 | 7 | 请选择下面三种方式之一进行部署 8 | 9 | - [通过命令行部署](/zh/guide/cli/pre-requisite) 10 | - [通过用户界面部署](/zh/guide/ui/d1) 11 | - [通过Github Actions 部署](/zh/guide/actions/pre-requisite) 12 | 13 | ### 也可以参考网友提供的详细的小白教程 14 | 15 | - [【教程】小白也能看懂的自建Cloudflare临时邮箱教程(域名邮箱)](https://linux.do/t/topic/316819/1) 16 | 17 | ## 升级流程 18 | 19 | 首先确认当前的版本,然后访问 [Release 页面](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/) 和 [CHANGELOG 页面](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md) 中找到当前的版本 20 | 21 | > [!WARNING] 注意 22 | > 需要注意 `Breaking Changes` 是必须进行 `数据库 sql 执行` 或者 `变量配置` 的 23 | 24 | 然后查看从当前版本往后的所有更改,需要注意 `Breaking Changes` 是必须进行 `数据库 sql 执行` 或者 `变量配置` 的, 其他的功能更新按需配置即可 25 | 26 | 然后参考下面的文档使用 `CLI` 或者 `UI` 覆盖部署之前的 `worker` 和 `pages` 即可 27 | 28 | ### CLI 部署 29 | 30 | - [命令行更新 d1](/zh/guide/cli/d1) 31 | - [命令行部署 worker](/zh/guide/cli/worker) 32 | - [命令行部署 pages](/zh/guide/cli/worker) 33 | 34 | ### UI 部署 35 | 36 | - [用户界面更新 d1](/zh/guide/ui/d1) 37 | - [用户界面部署 worker](/zh/guide/ui/worker) 38 | - [用户界面部署 pages](/zh/guide/ui/pages) 39 | 40 | ### Github Actions 部署 41 | 42 | - [Github Actions 部署如何配置自动更新](/zh/guide/actions/auto-update) 43 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/ui/d1.md: -------------------------------------------------------------------------------- 1 | # Initialize/Update D1 Database 2 | 3 | ## Create Database 4 | 5 | Open the Cloudflare console, select `Storage & Databases` -> `D1 SQL Database` -> `Create Database`, and click to create the database. 6 | 7 | ![d1](/ui_install/d1.png) 8 | 9 | After creation, we can see the D1 database in the Cloudflare console. 10 | 11 | ## Initialize Database 12 | 13 | ::: warning Note 14 | You can also skip initializing the database and after deployment is complete, go to the admin page's `Quick Setup` -> `Database` section and click the `Initialize Database` button to initialize the database. 15 | ::: 16 | 17 | Open the `Console` tab, enter the content from the [db/schema.sql](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/db/schema.sql) file in the repository, and click `Execute` to run it. 18 | 19 | ![d1](/ui_install/d1-exec.png) 20 | 21 | ## Update Database Schema 22 | 23 | For `schema` updates, please confirm the version you previously deployed. 24 | 25 | Check the [Changelog](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md) 26 | 27 | Find the `patch` file that needs to be executed, for example: `db/2024-01-13-patch.sql` 28 | 29 | Open the `Console` tab, enter the content of the `patch` file, and click `Execute` to run it. 30 | 31 | ![d1](/ui_install/d1-exec.png) 32 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Temporary Email Docs" 7 | tagline: "Build Free CloudFlare Temporary Domain Email with Send & Receive" 8 | actions: 9 | - theme: brand 10 | text: Try it now 11 | link: https://mail.awsl.uk/ 12 | - theme: alt 13 | text: CLI Deployment 14 | link: /en/guide/quick-start 15 | - theme: alt 16 | text: Deploy via UI 17 | link: /en/guide/quick-start 18 | - theme: alt 19 | text: Deploy via Github Actions 20 | link: /en/guide/quick-start 21 | 22 | features: 23 | - title: Private deployment with only a domain name, free hosting on CloudFlare, no server required 24 | details: Support password login for mailboxes, user registration, access password for private sites, attachment support. 25 | - title: Email parsing using Rust WASM 26 | details: Parse emails with Rust WASM, support various RFC email standards, support attachments, extremely fast 27 | - title: Telegram Bot and Webhook support 28 | details: Forward emails to Telegram or webhook, Telegram Bot supports mailbox binding, view emails, Telegram Mini App 29 | - title: Send emails (UI/API/SMTP) 30 | details: Send txt or html emails via domain mailboxes, DKIM signature support, send via UI/API/SMTP 31 | --- 32 | -------------------------------------------------------------------------------- /worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudflare_temp_email", 3 | "version": "1.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "wrangler dev", 8 | "lint": "eslint src", 9 | "deploy": "wrangler deploy --minify", 10 | "start": "wrangler dev", 11 | "build": "wrangler deploy --dry-run --outdir dist --minify" 12 | }, 13 | "devDependencies": { 14 | "@cloudflare/workers-types": "^4.20251205.0", 15 | "@eslint/js": "9.18.0", 16 | "@simplewebauthn/types": "10.0.0", 17 | "@types/node": "^22.19.1", 18 | "eslint": "9.18.0", 19 | "globals": "^15.15.0", 20 | "typescript-eslint": "^8.48.1", 21 | "wrangler": "^4.53.0" 22 | }, 23 | "dependencies": { 24 | "@aws-sdk/client-s3": "3.888.0", 25 | "@aws-sdk/s3-request-presigner": "3.888.0", 26 | "@simplewebauthn/server": "10.0.1", 27 | "hono": "^4.10.7", 28 | "jsonpath-plus": "^10.3.0", 29 | "mimetext": "^3.0.27", 30 | "postal-mime": "^2.6.1", 31 | "resend": "^4.8.0", 32 | "telegraf": "4.16.3", 33 | "worker-mailer": "^1.2.1" 34 | }, 35 | "pnpm": { 36 | "patchedDependencies": { 37 | "telegraf@4.16.3": "patches/telegraf@4.16.3.patch" 38 | } 39 | }, 40 | "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39" 41 | } 42 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/config-smtp-proxy.md: -------------------------------------------------------------------------------- 1 | # 搭建 SMTP IMAP 代理服务 2 | 3 | ::: warning 注意 4 | 如果你使用了 `resend`, 可直接使用 `resend` 的 `SMTP` 服务,不需要使用此服务 5 | ::: 6 | 7 | ## 为什么需要 SMTP IMAP 代理服务 8 | 9 | `SMTP` `IMAP` 的应用场景更加广泛 10 | 11 | ## 如何搭建 SMTP IMAP 代理服务 12 | 13 | ### Local Run 14 | 15 | ```bash 16 | cd smtp_proxy_server/ 17 | # 复制配置文件, 并修改配置文件 18 | # 你的 worker 地址,proxy_url=https://temp-email-api.xxx.xxx 19 | # 你的 SMTP 服务端口,port=8025 20 | cp .env.example .env 21 | python3 -m venv venv 22 | ./venv/bin/python3 -m pip install -r requirements.txt 23 | ./venv/bin/python3 main.py 24 | ``` 25 | 26 | ### Docker Run 27 | 28 | ```bash 29 | cd smtp_proxy_server/ 30 | docker-compose up -d 31 | ``` 32 | 33 | 修改 docker-compose.yaml 中的环境变量, 注意选择合适的 `tag` 34 | 35 | `proxy_url` 为 `worker` 的 URL 地址 36 | 37 | ```yaml 38 | services: 39 | smtp_proxy_server: 40 | image: ghcr.io/dreamhunter2333/cloudflare_temp_email/smtp_proxy_server:latest 41 | # build: 42 | # context: . 43 | # dockerfile: dockerfile 44 | container_name: "smtp_proxy_server" 45 | ports: 46 | - "8025:8025" 47 | - "11143:11143" 48 | environment: 49 | - proxy_url=https://temp-email-api.xxx.xxx 50 | - port=8025 51 | - imap_port=11143 52 | ``` 53 | 54 | ## 使用 Thunderbird 登录 55 | 56 | 下载 [Thunderbird](https://www.thunderbird.net/en-US/) 57 | 58 | 密码填写 `邮箱地址凭证` 59 | 60 | ![imap](/feature/imap.png) 61 | -------------------------------------------------------------------------------- /worker/src/telegram_api/settings.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { CONSTANTS } from "../constants"; 3 | 4 | export class TelegramSettings { 5 | enableAllowList: boolean; 6 | allowList: string[]; 7 | miniAppUrl: string; 8 | enableGlobalMailPush: boolean; 9 | globalMailPushList: string[]; 10 | 11 | constructor( 12 | enableAllowList: boolean, allowList: string[], miniAppUrl: string, 13 | enableGlobalMailPush: boolean, globalMailPushList: string[] 14 | ) { 15 | this.enableAllowList = enableAllowList; 16 | this.allowList = allowList; 17 | this.miniAppUrl = miniAppUrl; 18 | this.enableGlobalMailPush = enableGlobalMailPush; 19 | this.globalMailPushList = globalMailPushList; 20 | } 21 | } 22 | 23 | async function getTelegramSettings(c: Context): Promise { 24 | const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json"); 25 | return c.json(settings || new TelegramSettings(false, [], "", false, [])); 26 | } 27 | 28 | 29 | async function saveTelegramSettings(c: Context): Promise { 30 | const settings = await c.req.json(); 31 | await c.env.KV.put(CONSTANTS.TG_KV_SETTINGS_KEY, JSON.stringify(settings)); 32 | return c.json({ success: true }) 33 | } 34 | 35 | export default { 36 | getTelegramSettings, 37 | saveTelegramSettings, 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/smtp_proxy_server.yml: -------------------------------------------------------------------------------- 1 | name: SMTP Proxy Server Docker Image CI 2 | 3 | on: 4 | push: 5 | paths: 6 | - "smtp_proxy_server/**" 7 | tags: 8 | - "*" 9 | branches: 10 | - main 11 | workflow_dispatch: 12 | 13 | env: 14 | REGISTRY: ghcr.io 15 | IMAGE_NAME: smtp_proxy_server 16 | 17 | jobs: 18 | build-and-push-image: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | packages: write 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Log in to the Container registry 27 | uses: docker/login-action@v3 28 | with: 29 | registry: ${{ env.REGISTRY }} 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Set up QEMU 34 | uses: docker/setup-qemu-action@v3 35 | 36 | - name: Set up Docker Buildx 37 | uses: docker/setup-buildx-action@v3 38 | 39 | - name: Build and push Docker images 40 | uses: docker/build-push-action@v5 41 | with: 42 | context: ./smtp_proxy_server 43 | file: ./smtp_proxy_server/dockerfile 44 | platforms: linux/amd64,linux/arm64 45 | push: true 46 | tags: | 47 | ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} 48 | ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.IMAGE_NAME }}:latest 49 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/mail-api.md: -------------------------------------------------------------------------------- 1 | # 查看邮件 API 2 | 3 | ## 通过 邮件 API 查看邮件 4 | 5 | 这是一个 `python` 的例子,使用 `requests` 库查看邮件。 6 | 7 | ```python 8 | limit = 10 9 | offset = 0 10 | res = requests.get( 11 | f"https://<你的worker地址>/api/mails?limit={limit}&offset={offset}", 12 | headers={ 13 | "Authorization": f"Bearer {你的JWT密码}", 14 | # "x-custom-auth": "<你的网站密码>", # 如果启用了自定义密码 15 | "Content-Type": "application/json" 16 | } 17 | ) 18 | ``` 19 | 20 | ## admin 邮件 API 21 | 22 | 支持 `address` filter 和 `keyword` filter 23 | 24 | ```python 25 | import requests 26 | 27 | url = "https://<你的worker地址>/admin/mails" 28 | 29 | querystring = { 30 | "limit":"20", 31 | "offset":"0", 32 | # adress 和 keyword 为可选参数 33 | "address":"xxxx@awsl.uk", 34 | "keyword":"xxxx" 35 | } 36 | 37 | headers = {"x-admin-auth": "<你的Admin密码>"} 38 | 39 | response = requests.get(url, headers=headers, params=querystring) 40 | 41 | print(response.json()) 42 | ``` 43 | 44 | ## user 邮件 API 45 | 46 | 支持 `address` filter 和 `keyword` filter 47 | 48 | ```python 49 | import requests 50 | 51 | url = "https://<你的worker地址>/user_api/mails" 52 | 53 | querystring = { 54 | "limit":"20", 55 | "offset":"0", 56 | # adress 和 keyword 为可选参数 57 | "address":"xxxx@awsl.uk", 58 | "keyword":"xxxx" 59 | } 60 | 61 | headers = {"x-admin-auth": "<你的Admin密码>"} 62 | 63 | response = requests.get(url, headers=headers, params=querystring) 64 | 65 | print(response.json()) 66 | ``` 67 | -------------------------------------------------------------------------------- /frontend/src/views/common/About.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 32 | 33 | 48 | -------------------------------------------------------------------------------- /worker/src/admin_api/oauth2_settings.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'hono'; 2 | 3 | import { CONSTANTS } from '../constants'; 4 | import { UserOauth2Settings } from "../models"; 5 | import { getJsonSetting, saveSetting } from '../utils'; 6 | 7 | async function getUserOauth2Settings(c: Context): Promise { 8 | const settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY); 9 | return c.json(settings || []); 10 | } 11 | 12 | async function saveUserOauth2Settings(c: Context): Promise { 13 | const settings = await c.req.json(); 14 | for (const setting of settings) { 15 | if (!setting.name || !setting.clientID || !setting.clientSecret 16 | || !setting.authorizationURL || !setting.accessTokenURL 17 | || !setting.accessTokenFormat 18 | || !setting.userInfoURL || !setting.redirectURL 19 | || !setting.userEmailKey || !setting.scope) { 20 | return c.text(`${setting.name} is missing required fields`, 400); 21 | } 22 | if (setting.enableMailAllowList && (setting.mailAllowList?.length || 0) < 1) { 23 | return c.text(`${setting.name} is missing mail allow list`, 400); 24 | } 25 | } 26 | await saveSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY, JSON.stringify(settings)); 27 | return c.json({ success: true }) 28 | } 29 | 30 | export default { 31 | getUserOauth2Settings, 32 | saveUserOauth2Settings, 33 | } 34 | -------------------------------------------------------------------------------- /vitepress-docs/docs/status.md: -------------------------------------------------------------------------------- 1 | # Status Page 2 | 3 | [Status Link](https://uptime.aks.awsl.icu/status/temp-email) 4 | 5 | | Service | Status | 6 | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 7 | | [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) ![](https://uptime.aks.awsl.icu/api/badge/10/ping) ![](https://uptime.aks.awsl.icu/api/badge/10/avg-response) ![](https://uptime.aks.awsl.icu/api/badge/10/cert-exp) ![](https://uptime.aks.awsl.icu/api/badge/10/response) | 8 | | [Frontend](https://mail.awsl.uk/) | ![](https://uptime.aks.awsl.icu/api/badge/12/status) ![](https://uptime.aks.awsl.icu/api/badge/12/uptime) ![](https://uptime.aks.awsl.icu/api/badge/12/ping) ![](https://uptime.aks.awsl.icu/api/badge/12/avg-response) ![](https://uptime.aks.awsl.icu/api/badge/12/cert-exp) ![](https://uptime.aks.awsl.icu/api/badge/12/response) | 9 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/status.md: -------------------------------------------------------------------------------- 1 | # Service Status 2 | 3 | [Status Link](https://uptime.aks.awsl.icu/status/temp-email) 4 | 5 | | Service | Status | 6 | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 7 | | [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) ![](https://uptime.aks.awsl.icu/api/badge/10/ping) ![](https://uptime.aks.awsl.icu/api/badge/10/avg-response) ![](https://uptime.aks.awsl.icu/api/badge/10/cert-exp) ![](https://uptime.aks.awsl.icu/api/badge/10/response) | 8 | | [Frontend](https://mail.awsl.uk/) | ![](https://uptime.aks.awsl.icu/api/badge/12/status) ![](https://uptime.aks.awsl.icu/api/badge/12/uptime) ![](https://uptime.aks.awsl.icu/api/badge/12/ping) ![](https://uptime.aks.awsl.icu/api/badge/12/avg-response) ![](https://uptime.aks.awsl.icu/api/badge/12/cert-exp) ![](https://uptime.aks.awsl.icu/api/badge/12/response) | 9 | -------------------------------------------------------------------------------- /worker/src/i18n/type.ts: -------------------------------------------------------------------------------- 1 | export type LocaleMessages = { 2 | CustomAuthPasswordMsg: string 3 | UserTokenExpiredMsg: string 4 | UserAcceesTokenExpiredMsg: string 5 | UserRoleIsNotAdminMsg: string 6 | NeedAdminPasswordMsg: string 7 | 8 | KVNotAvailableMsg: string 9 | DBNotAvailableMsg: string 10 | JWTSecretNotSetMsg: string 11 | WebhookNotEnabledMsg: string 12 | DomainsNotSetMsg: string 13 | 14 | TurnstileCheckFailedMsg: string 15 | NewAddressDisabledMsg: string 16 | NewAddressAnonymousDisabledMsg: string 17 | FailedCreateAddressMsg: string 18 | InvalidAddressMsg: string 19 | InvalidAddressCredentialMsg: string 20 | UserDeleteEmailDisabledMsg: string 21 | 22 | UserNotFoundMsg: string 23 | UserAlreadyExistsMsg: string 24 | FailedToRegisterMsg: string 25 | UserRegistrationDisabledMsg: string 26 | UserMailDomainMustInMsg: string 27 | InvalidVerifyCodeMsg: string 28 | InvalidEmailOrPasswordMsg: string 29 | VerifyMailSenderNotSetMsg: string 30 | CodeAlreadySentMsg: string 31 | InvalidUserDefaultRoleMsg: string 32 | FailedUpdateUserDefaultRoleMsg: string 33 | 34 | Oauth2ClientIDNotFoundMsg: string 35 | Oauth2CliendIDOrCodeMissingMsg: string 36 | Oauth2FailedGetUserInfoMsg: string 37 | Oauth2FailedGetAccessTokenMsg: string 38 | Oauth2FailedGetUserEmailMsg: string 39 | 40 | PasswordChangeDisabledMsg: string 41 | NewPasswordRequiredMsg: string 42 | InvalidAddressTokenMsg: string 43 | FailedUpdatePasswordMsg: string 44 | PasswordLoginDisabledMsg: string 45 | EmailPasswordRequiredMsg: string 46 | AddressNotFoundMsg: string 47 | } 48 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | 2 | # Cloudflare Temp Email 3 | 4 |

5 | 🇨🇳 中文 | 6 | 🇺🇸 English 7 |

8 | 9 | **A fully-featured temporary email service built on Cloudflare's free services.** 10 | 11 | > This project is for learning and personal use only. 12 | 13 | ## 🚀 Quick Start 14 | 15 | - [📖 Documentation](https://temp-mail-docs.awsl.uk/en/) 16 | - [🎯 Live Demo](https://mail.awsl.uk/) 17 | - [📝 CHANGELOG](CHANGELOG.md) 18 | 19 |

20 | 21 | Deploy to Cloudflare Workers 22 | 23 |

24 | 25 | ## ✨ Key Features 26 | 27 | - **📧 Email Processing**: Rust WASM parser, **AI email extraction** (verification codes, auth links), SMTP/IMAP support, attachments, auto-reply 28 | - **👥 User Management**: OAuth2 login, Passkey authentication, role management 29 | - **🌐 Admin Panel**: Complete admin console, user management, scheduled cleanup 30 | - **🤖 Integrations**: Telegram Bot, webhooks, CAPTCHA, rate limiting 31 | - **� Modern UI**: Multi-language, responsive design, JWT auto-login 32 | 33 | ## 🏗️ Tech Stack 34 | 35 | - **Frontend**: Vue 3 + TypeScript + Vite 36 | - **Backend**: Cloudflare Workers + D1 Database 37 | - **Email**: Cloudflare Email Routing + Rust WASM Parser 38 | - **Storage**: Cloudflare KV + R2 (optional S3) 39 | 40 | ## 🌟 Community 41 | 42 | - [Telegram](https://t.me/cloudflare_temp_email) 43 | 44 | ## 📄 License 45 | 46 | MIT License - see [LICENSE](LICENSE) for details. 47 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/mail-api.md: -------------------------------------------------------------------------------- 1 | # Mail API 2 | 3 | ## Viewing Emails via Mail API 4 | 5 | This is a `python` example using the `requests` library to view emails. 6 | 7 | ```python 8 | limit = 10 9 | offset = 0 10 | res = requests.get( 11 | f"https:///api/mails?limit={limit}&offset={offset}", 12 | headers={ 13 | "Authorization": f"Bearer {your-JWT-password}", 14 | # "x-custom-auth": "", # If custom password is enabled 15 | "Content-Type": "application/json" 16 | } 17 | ) 18 | ``` 19 | 20 | ## Admin Mail API 21 | 22 | Supports `address` filter and `keyword` filter 23 | 24 | ```python 25 | import requests 26 | 27 | url = "https:///admin/mails" 28 | 29 | querystring = { 30 | "limit":"20", 31 | "offset":"0", 32 | # address and keyword are optional parameters 33 | "address":"xxxx@awsl.uk", 34 | "keyword":"xxxx" 35 | } 36 | 37 | headers = {"x-admin-auth": ""} 38 | 39 | response = requests.get(url, headers=headers, params=querystring) 40 | 41 | print(response.json()) 42 | ``` 43 | 44 | ## User Mail API 45 | 46 | Supports `address` filter and `keyword` filter 47 | 48 | ```python 49 | import requests 50 | 51 | url = "https:///user_api/mails" 52 | 53 | querystring = { 54 | "limit":"20", 55 | "offset":"0", 56 | # address and keyword are optional parameters 57 | "address":"xxxx@awsl.uk", 58 | "keyword":"xxxx" 59 | } 60 | 61 | headers = {"x-admin-auth": ""} 62 | 63 | response = requests.get(url, headers=headers, params=querystring) 64 | 65 | print(response.json()) 66 | ``` 67 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/config-smtp-proxy.md: -------------------------------------------------------------------------------- 1 | # Setting Up SMTP IMAP Proxy Service 2 | 3 | ::: warning Notice 4 | If you are using `resend`, you can directly use `resend`'s `SMTP` service without needing this service 5 | ::: 6 | 7 | ## Why Do You Need SMTP IMAP Proxy Service 8 | 9 | `SMTP` and `IMAP` have a wider range of application scenarios 10 | 11 | ## How to Set Up SMTP IMAP Proxy Service 12 | 13 | ### Local Run 14 | 15 | ```bash 16 | cd smtp_proxy_server/ 17 | # Copy configuration file and modify it 18 | # Your worker address, proxy_url=https://temp-email-api.xxx.xxx 19 | # Your SMTP service port, port=8025 20 | cp .env.example .env 21 | python3 -m venv venv 22 | ./venv/bin/python3 -m pip install -r requirements.txt 23 | ./venv/bin/python3 main.py 24 | ``` 25 | 26 | ### Docker Run 27 | 28 | ```bash 29 | cd smtp_proxy_server/ 30 | docker-compose up -d 31 | ``` 32 | 33 | Modify the environment variables in docker-compose.yaml, note to choose the appropriate `tag` 34 | 35 | `proxy_url` is the URL address of the `worker` 36 | 37 | ```yaml 38 | services: 39 | smtp_proxy_server: 40 | image: ghcr.io/dreamhunter2333/cloudflare_temp_email/smtp_proxy_server:latest 41 | # build: 42 | # context: . 43 | # dockerfile: dockerfile 44 | container_name: "smtp_proxy_server" 45 | ports: 46 | - "8025:8025" 47 | - "11143:11143" 48 | environment: 49 | - proxy_url=https://temp-email-api.xxx.xxx 50 | - port=8025 51 | - imap_port=11143 52 | ``` 53 | 54 | ## Using Thunderbird to Login 55 | 56 | Download [Thunderbird](https://www.thunderbird.net/en-US/) 57 | 58 | For password, enter the `email address credential` 59 | 60 | ![imap](/feature/imap.png) 61 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/cli/pages.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Pages Frontend 2 | 3 | > [!warning] Notice 4 | > Choose one of the following methods 5 | 6 | ## Deploy Worker with Frontend Assets 7 | 8 | Refer to [Deploy Worker](/en/guide/cli/worker#deploy-worker-with-frontend-optional) 9 | 10 | ## Separate Frontend and Backend Deployment 11 | 12 | The first deployment will prompt you to create a project. For the `production` branch, enter `production`. 13 | 14 | ```bash 15 | cd frontend 16 | pnpm install 17 | cp .env.example .env.prod 18 | ``` 19 | 20 | Modify the `.env.prod` file. 21 | 22 | Change `VITE_API_BASE` to the `worker` `url` created in the previous step. Do not add `/` at the end. 23 | 24 | For example: `VITE_API_BASE=https://xxx.xxx.workers.dev` 25 | 26 | ```bash 27 | pnpm build --emptyOutDir 28 | # The first deployment will prompt you to create a project, for production branch enter production 29 | pnpm run deploy 30 | ``` 31 | 32 | After deployment, you can see your project in the Cloudflare console. You can configure a custom domain for `pages`. 33 | 34 | ![pages](/readme_assets/pages.png) 35 | 36 | ## Forward Backend Requests Through Page Functions 37 | 38 | Forwarding requests from page functions to the worker backend can achieve faster response times. 39 | 40 | The first deployment will prompt you to create a project. For the `production` branch, enter `production`. 41 | 42 | If your worker backend name is not `cloudflare_temp_email`, please modify `pages/wrangler.toml`. 43 | 44 | ```bash 45 | cd frontend 46 | pnpm install 47 | # If you want to enable Cloudflare Zero Trust, you need to use pnpm build:pages:nopwa to disable caching 48 | pnpm build:pages 49 | cd ../pages 50 | pnpm run deploy 51 | ``` 52 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/webhook.md: -------------------------------------------------------------------------------- 1 | # Configure Webhook 2 | 3 | > [!NOTE] 4 | > If you want to use webhook, please bind `KV` first and configure the `worker` variable `ENABLE_WEBHOOK = true` 5 | > 6 | > If you want webhook to have stronger email parsing capabilities, refer to [Configure worker to use wasm for email parsing](/en/guide/feature/mail_parser_wasm_worker) 7 | 8 | ## Prerequisites 9 | 10 | You need to set up your own `webhook service` or use a `third-party platform`. This service needs to be able to receive `POST` requests and parse `json` data. 11 | 12 | This project uses [songquanpeng/message-pusher](https://github.com/songquanpeng/message-pusher) as an example webhook service. 13 | 14 | - You can use the service provided by [msgpusher.com](https://msgpusher.com) 15 | - You can also self-host the `message-pusher` service, refer to [songquanpeng/message-pusher](https://github.com/songquanpeng/message-pusher) 16 | 17 | ## Admin Configure Global Webhook 18 | 19 | ![telegram](/feature/admin-mail-webhook.png) 20 | 21 | ## Admin Allow Email to Use Webhook 22 | 23 | ![telegram](/feature/admin-webhook-settings.png) 24 | 25 | ## Configure Webhook for a Specific Email 26 | 27 | ![telegram](/feature/address-webhook.png) 28 | 29 | ## Webhook Data Format 30 | 31 | To get the url, you need to configure the worker's `FRONTEND_URL` to your frontend address, or you can construct the url yourself using `id` = `${FRONTEND_URL}?mail_id=${id}` 32 | 33 | ```json 34 | { 35 | "id": "${id}", 36 | "url": "${url}", 37 | "from": "${from}", 38 | "to": "${to}", 39 | "subject": "${subject}", 40 | "raw": "${raw}", 41 | "parsedText": "${parsedText}", 42 | "parsedHtml": "${parsedHtml}", 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /worker/src/admin_api/admin_mail_api.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { handleListQuery } from "../common"; 3 | 4 | export default { 5 | getMails: async (c: Context) => { 6 | const { address, limit, offset, keyword } = c.req.query(); 7 | const addressQuery = address ? `address = ?` : ""; 8 | const addressParams = address ? [address] : []; 9 | const keywordQuery = keyword ? `raw like ?` : ""; 10 | const keywordParams = keyword ? [`%${keyword}%`] : []; 11 | const filterQuerys = [addressQuery, keywordQuery].filter((item) => item).join(" and "); 12 | const finalQuery = filterQuerys.length > 0 ? `where ${filterQuerys}` : ""; 13 | const filterParams = [...addressParams, ...keywordParams] 14 | return await handleListQuery(c, 15 | `SELECT * FROM raw_mails ${finalQuery}`, 16 | `SELECT count(*) as count FROM raw_mails ${finalQuery}`, 17 | filterParams, limit, offset 18 | ); 19 | }, 20 | getUnknowMails: async (c: Context) => { 21 | const { limit, offset } = c.req.query(); 22 | return await handleListQuery(c, 23 | `SELECT * FROM raw_mails where address NOT IN (select name from address) `, 24 | `SELECT count(*) as count FROM raw_mails` 25 | + ` where address NOT IN (select name from address) `, 26 | [], limit, offset 27 | ); 28 | }, 29 | deleteMail: async (c: Context) => { 30 | const { id } = c.req.param(); 31 | const { success } = await c.env.DB.prepare( 32 | `DELETE FROM raw_mails WHERE id = ? ` 33 | ).bind(id).run(); 34 | return c.json({ 35 | success: success 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/views/admin/SendBox.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 48 | 49 | 55 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/telegram.md: -------------------------------------------------------------------------------- 1 | # 配置 Telegram Bot 2 | 3 | 试用地址:[@cf_temp_mail_bot](https://t.me/cf_temp_mail_bot) 4 | 5 | ::: warning 注意 6 | worker 默认的 `worker.dev` 域名的证书是不被 telegram 支持的,配置 Telegram Bot 请使用自定义域名 7 | ::: 8 | 9 | > [!NOTE] 10 | > 如果要使用 Telegram Bot, 请先绑定 `KV` 11 | > 12 | > 如果不需要 Telegram Bot, 可跳过此步骤 13 | > 14 | > 如果你想 Telegram 的解析邮件能力更强,参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker) 15 | 16 | ## Telegram Bot 配置 17 | 18 | 请先创建一个 Telegram Bot,然后获取 `token`,然后执行下面的命令,将 `token` 添加到 secrets 中 19 | 20 | > [!NOTE] 21 | > 如果你觉得麻烦,也可以直接明文放在 `wrangler.toml` 中 `[vars]` 下面,但是不推荐这样做 22 | 23 | 如果你是通过 UI 部署的,可以在 Cloudflare 的 UI 界面中添加到 `Variables and Secrets` 下面 24 | 25 | ```bash 26 | # 切换到 worker 目录 27 | cd worker 28 | pnpm wrangler secret put TELEGRAM_BOT_TOKEN 29 | ``` 30 | 31 | ## Bot 32 | 33 | - 可设置白名单用户 34 | - 点击`初始化`即可完成配置。 35 | - 点击`查看状态`,可以查看当前配置的状态。 36 | 37 | ![telegram](/feature/telegram.png) 38 | 39 | ## Mini App 40 | 41 | 可以通过命令行部署,或者 UI 界面部署 42 | 43 | ### UI 部署 44 | 45 | 其他步骤参考 [UI 部署](/zh/guide/cli/pages) 中的 `前后端分离部署` 46 | 47 | > [!NOTE] 48 | > 从这里下载 zip, [telegram-frontend.zip](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/telegram-frontend.zip) 49 | > 50 | > 修改压缩包里面的 index-xxx.js 文件 ,xx 是随机的字符串 51 | > 52 | > 搜索 `https://temp-email-api.xxx.xxx` ,替换成你worker 的域名,然后部署新的zip文件 53 | 54 | ### 命令行部署 55 | 56 | ```bash 57 | cd frontend 58 | pnpm install 59 | cp .env.example .env.prod 60 | # --project-name 可以单独为 mini app 创建一个 pages, 你也可以公用一个 pages,但是可能遇到 js 加载不了的问题 61 | pnpm run deploy:telegram --project-name=<你的项目名称> 62 | ``` 63 | 64 | - 部署完成后,请在 admin 后台的 `设置` -> `电报小程序` 页面 `电报小程序 URL` 中填写网页 URL。 65 | - 请在 `@BotFather` 处执行 `/setmenubutton`,然后输入你的网页地址,设置左下角的 `Open App` 按钮。 66 | - 请在 `@BotFather` 处执行 `/newapp` 新建 app 来注册 mini app。 67 | -------------------------------------------------------------------------------- /worker/src/email/auto_reply.ts: -------------------------------------------------------------------------------- 1 | import { createMimeMessage } from "mimetext"; 2 | import { getBooleanValue } from "../utils"; 3 | 4 | export const auto_reply = async (message: ForwardableEmailMessage, env: Bindings): Promise => { 5 | const message_id = message.headers.get("Message-ID"); 6 | // auto reply email 7 | if (getBooleanValue(env.ENABLE_AUTO_REPLY) && message_id) { 8 | try { 9 | const results = await env.DB.prepare( 10 | `SELECT * FROM auto_reply_mails where address = ? and enabled = 1` 11 | ).bind(message.to).first>(); 12 | if (results && results.source_prefix && message.from.startsWith(results.source_prefix)) { 13 | const msg = createMimeMessage(); 14 | msg.setHeader("In-Reply-To", message_id); 15 | msg.setSender({ 16 | name: results.name || results.address, 17 | addr: results.address 18 | }); 19 | msg.setRecipient(message.from); 20 | msg.setSubject(results.subject || "Auto-reply"); 21 | msg.addMessage({ 22 | contentType: 'text/plain', 23 | data: results.message || "This is an auto-reply message, please reconact later." 24 | }); 25 | const { EmailMessage } = await import('cloudflare:email'); 26 | const replyMessage = new EmailMessage( 27 | message.to, 28 | message.from, 29 | msg.asRaw() 30 | ); 31 | // @ts-ignore 32 | await message.reply(replyMessage); 33 | } 34 | } catch (error) { 35 | console.log("reply email error", error); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import Index from '../views/Index.vue' 3 | import User from '../views/User.vue' 4 | import UserOauth2Callback from '../views/user/UserOauth2Callback.vue' 5 | import i18n from '../i18n' 6 | import { useGlobalState } from '../store' 7 | 8 | const { jwt } = useGlobalState() 9 | 10 | const router = createRouter({ 11 | history: createWebHistory(), 12 | routes: [ 13 | { 14 | path: '/', 15 | alias: "/:lang/", 16 | component: Index 17 | }, 18 | { 19 | path: '/user', 20 | alias: "/:lang/user", 21 | component: User 22 | }, 23 | { 24 | path: '/user/oauth2/callback', 25 | alias: "/:lang/user/oauth2/callback", 26 | component: UserOauth2Callback 27 | }, 28 | { 29 | path: '/admin', 30 | alias: "/:lang/admin", 31 | component: () => import('../views/Admin.vue') 32 | }, 33 | { 34 | path: '/telegram_mail', 35 | alias: "/:lang/telegram_mail", 36 | component: () => import('../views/telegram/Mail.vue') 37 | }, 38 | { 39 | name: 'not-found', 40 | path: '/:pathMatch(.*)*', 41 | redirect: '/' 42 | } 43 | ] 44 | }); 45 | 46 | 47 | router.beforeEach((to, from, next) => { 48 | if (to.params.lang && ['en', 'zh'].includes(to.params.lang)) { 49 | i18n.global.locale.value = to.params.lang 50 | } else { 51 | i18n.global.locale.value = 'zh' 52 | } 53 | // check if query parameter has jwt, set it to store 54 | if (to.query.jwt) { 55 | jwt.value = to.query.jwt; 56 | } 57 | next() 58 | }); 59 | 60 | export default router 61 | -------------------------------------------------------------------------------- /frontend/src/views/User.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 54 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/send-mail-api.md: -------------------------------------------------------------------------------- 1 | # 发送邮件 API 2 | 3 | ## 通过 HTTP API 发送邮件 4 | 5 | 这是一个 `python` 的例子,使用 `requests` 库发送邮件。 6 | 7 | ```python 8 | send_body = { 9 | "from_name": "发件人名字", 10 | "to_name": "收件人名字", 11 | "to_mail": "收件人地址", 12 | "subject": "邮件主题", 13 | "is_html": False, # 根据内容设置是否为 HTML 14 | "content": "<邮件内容:html 或者 文本>", 15 | } 16 | 17 | res = requests.post( 18 | "http://localhost:8787/api/send_mail", 19 | json=send_body, headers={ 20 | "Authorization": f"Bearer {你的JWT密码}", 21 | # "x-custom-auth": "<你的网站密码>", # 如果启用了自定义密码 22 | "Content-Type": "application/json" 23 | } 24 | ) 25 | 26 | # 使用 body 验证 27 | send_body = { 28 | "token": "<你的JWT密码>", 29 | "from_name": "发件人名字", 30 | "to_name": "收件人名字", 31 | "to_mail": "收件人地址", 32 | "subject": "邮件主题", 33 | "is_html": False, # 根据内容设置是否为 HTML 34 | "content": "<邮件内容:html 或者 文本>", 35 | } 36 | res = requests.post( 37 | "http://localhost:8787/external/api/send_mail", 38 | json=send_body, headers={ 39 | "Content-Type": "application/json" 40 | } 41 | ) 42 | ``` 43 | 44 | ## 通过 SMTP 发送邮件 45 | 46 | 请先参考 [配置 SMTP 代理](/zh/guide/feature/config-smtp-proxy.html)。 47 | 48 | 这是一个 `python` 的例子,使用 `smtplib` 库发送邮件。 49 | 50 | `JWT令牌密码`: 即为邮箱登录密码,可以在 UI 界面中查看密码菜单中查看。 51 | 52 | ```python 53 | import smtplib 54 | 55 | from email.mime.text import MIMEText 56 | from email.mime.multipart import MIMEMultipart 57 | 58 | 59 | with smtplib.SMTP('localhost', 8025) as smtp: 60 | smtp.login("jwt", "此处填写你的JWT令牌密码") 61 | message = MIMEMultipart() 62 | message['From'] = "Me " 63 | message['To'] = "Admin " 64 | message['Subject'] = "测试主题" 65 | message.attach(MIMEText("测试内容", 'html')) 66 | smtp.sendmail("me@awsl.uk", "admin@awsl.uk", message.as_string()) 67 | ``` 68 | -------------------------------------------------------------------------------- /worker/src/user_api/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | 3 | import settings from './settings'; 4 | import user from './user'; 5 | import bind_address from './bind_address'; 6 | import passkey from './passkey'; 7 | import oauth2 from './oauth2'; 8 | import user_mail_api from './user_mail_api'; 9 | 10 | export const api = new Hono(); 11 | 12 | // settings api 13 | api.get('/user_api/open_settings', settings.openSettings); 14 | api.get('/user_api/settings', settings.settings); 15 | 16 | // mail api 17 | api.get('/user_api/mails', user_mail_api.getMails); 18 | api.delete('/user_api/mails/:id', user_mail_api.deleteMail); 19 | 20 | // user api 21 | api.post('/user_api/login', user.login); 22 | api.post('/user_api/verify_code', user.verifyCode); 23 | api.post('/user_api/register', user.register); 24 | 25 | // oauth2 api 26 | api.get('/user_api/oauth2/login_url', oauth2.getOauth2LoginUrl); 27 | api.post('/user_api/oauth2/callback', oauth2.oauth2Login); 28 | 29 | // bind address api 30 | api.get('/user_api/bind_address', bind_address.getBindedAddresses); 31 | api.post('/user_api/bind_address', bind_address.bind); 32 | api.get('/user_api/bind_address_jwt/:address_id', bind_address.getBindedAddressJwt); 33 | api.post('/user_api/unbind_address', bind_address.unbind); 34 | api.post('/user_api/transfer_address', bind_address.transferAddress); 35 | 36 | // passkey api 37 | api.get('/user_api/passkey', passkey.getPassKeys); 38 | api.post('/user_api/passkey/rename', passkey.renamePassKey); 39 | api.delete('/user_api/passkey/:passkey_id', passkey.deletePassKey); 40 | api.post('/user_api/passkey/register_request', passkey.registerRequest); 41 | api.post('/user_api/passkey/register_response', passkey.registerResponse); 42 | api.post('/user_api/passkey/authenticate_request', passkey.authenticateRequest); 43 | api.post('/user_api/passkey/authenticate_response', passkey.authenticateResponse); 44 | -------------------------------------------------------------------------------- /worker/src/email/check_attachment.ts: -------------------------------------------------------------------------------- 1 | import { getBooleanValue } from "../utils"; 2 | import { commonParseMail } from "../common"; 3 | import { createMimeMessage } from "mimetext"; 4 | 5 | export const remove_attachment_if_need = async ( 6 | env: Bindings, 7 | parsedEmailContext: ParsedEmailContext, 8 | from_address: string, 9 | to_address: string, 10 | size: number 11 | ): Promise => { 12 | // if configured, remove all attachment 13 | const removeAllAttachment = getBooleanValue(env.REMOVE_ALL_ATTACHMENT); 14 | // if attachment size > 2MB, remove attachment 15 | const removeExceedSizeAttachment = getBooleanValue(env.REMOVE_EXCEED_SIZE_ATTACHMENT) && size >= 2 * 1024 * 1024; 16 | const shouldRemoveAttachment = removeAllAttachment || removeExceedSizeAttachment; 17 | if (!shouldRemoveAttachment) return; 18 | 19 | const parsedEmail = await commonParseMail(parsedEmailContext); 20 | if (!parsedEmail) return; 21 | 22 | const msg = createMimeMessage(); 23 | if (parsedEmail?.headers) { 24 | for (const header of parsedEmail.headers) { 25 | try { 26 | msg.setHeader(header["key"], header["value"]); 27 | } catch (error) { 28 | // ignore 29 | } 30 | } 31 | } 32 | msg.setSender({ 33 | name: parsedEmail?.sender || from_address, 34 | addr: from_address 35 | }); 36 | msg.setRecipient(to_address); 37 | msg.setSubject(parsedEmail?.subject || "Failed to parse email subject"); 38 | if (parsedEmail?.html) { 39 | msg.addMessage({ 40 | contentType: 'text/html', 41 | data: parsedEmail.html 42 | }); 43 | } 44 | if (parsedEmail?.text) { 45 | msg.addMessage({ 46 | contentType: 'text/plain', 47 | data: parsedEmail.text 48 | }); 49 | } 50 | parsedEmailContext.rawEmail = msg.asRaw(); 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/views/user/UserOauth2Callback.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 66 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/quick-start.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | ## Before You Begin 4 | 5 | You need a `good network environment` and a `Cloudflare account`. Open the [Cloudflare Dashboard](https://dash.cloudflare.com/) 6 | 7 | Please choose one of the three deployment methods below: 8 | 9 | - [Deploy via Command Line](/en/guide/cli/pre-requisite) 10 | - [Deploy via User Interface](/en/guide/ui/d1) 11 | - [Deploy via Github Actions](/en/guide/actions/pre-requisite) 12 | 13 | ### You can also refer to detailed tutorials provided by the community 14 | 15 | - [【Tutorial】Beginner-Friendly Guide to Building Your Own Cloudflare Temporary Email (Domain Email)](https://linux.do/t/topic/316819/1) 16 | 17 | ## Upgrade Process 18 | 19 | First, confirm your current version, then visit the [Release page](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/) and [CHANGELOG page](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md) to find your current version. 20 | 21 | > [!WARNING] Warning 22 | > Pay attention to `Breaking Changes` which require `database SQL execution` or `configuration changes`. 23 | 24 | Then review all changes from your current version onwards. Note that `Breaking Changes` require `database SQL execution` or `configuration changes`, while other feature updates can be configured as needed. 25 | 26 | Then refer to the documentation below to use `CLI` or `UI` to redeploy the `worker` and `pages` over the previous deployment. 27 | 28 | ### CLI Deployment 29 | 30 | - [Update D1 via Command Line](/en/guide/cli/d1) 31 | - [Deploy Worker via Command Line](/en/guide/cli/worker) 32 | - [Deploy Pages via Command Line](/en/guide/cli/pages) 33 | 34 | ### UI Deployment 35 | 36 | - [Update D1 via User Interface](/en/guide/ui/d1) 37 | - [Deploy Worker via User Interface](/en/guide/ui/worker) 38 | - [Deploy Pages via User Interface](/en/guide/ui/pages) 39 | 40 | ### Github Actions Deployment 41 | 42 | - [How to Configure Auto-Update with Github Actions](/en/guide/actions/auto-update) 43 | -------------------------------------------------------------------------------- /vitepress-docs/docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | import { zh } from './zh' 3 | import { en } from './en' 4 | 5 | export default defineConfig({ 6 | title: "Temp Mail Doc", 7 | lang: 'zh-CN', 8 | lastUpdated: true, 9 | locales: { 10 | root: { label: '简体中文', ...zh }, 11 | en: { label: 'English', ...en } 12 | }, 13 | head: [ 14 | ['link', { rel: 'icon', type: 'image/png', href: '/logo.png' }], 15 | ['meta', { name: 'theme-color', content: '#5f67ee' }], 16 | ['meta', { property: 'og:type', content: 'website' }], 17 | ['meta', { property: 'og:locale', content: 'Temp Mail Doc' }], 18 | ['meta', { property: 'og:title', content: 'Temp Mail Doc' }], 19 | ['meta', { property: 'og:site_name', content: 'Temp Mail' }], 20 | ['meta', { property: 'og:image', content: 'https://temp-mail-docs.awsl.uk/logo.png' }], 21 | ['meta', { property: 'og:url', content: 'https://temp-mail-docs.awsl.uk' }], 22 | ], 23 | sitemap: { 24 | hostname: 'https://temp-mail-docs.awsl.uk', 25 | transformItems(items) { 26 | return items.filter((item) => !item.url.includes('migration')) 27 | } 28 | }, 29 | themeConfig: { 30 | logo: { src: '/logo.png', width: 24, height: 24 }, 31 | search: { provider: 'local' }, 32 | socialLinks: [ 33 | { 34 | icon: 'discord', 35 | link: 'https://discord.gg/dQEwTWhA6Q' 36 | }, 37 | { 38 | icon: { 39 | svg: '' 40 | }, 41 | link: 'https://t.me/cloudflare_temp_email' 42 | }, 43 | { 44 | icon: 'github', 45 | link: 'https://github.com/dreamhunter2333/cloudflare_temp_email' 46 | } 47 | ] 48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/config-send-mail.md: -------------------------------------------------------------------------------- 1 | 2 | # 配置发送邮件 3 | 4 | ::: warning 注意 5 | 三种方式可以同时配置,发送邮件时会优先使用 `resend`,如果没有配置 `resend`,则会使用 `smtp`. 6 | 7 | 如果配置了 Cloudflare 已认证的转发邮箱地址,会优先使用 cf 内部 API 发送邮件 8 | ::: 9 | 10 | ## 使用 resend 发送邮件 11 | 12 | 注册 `https://resend.com/domains` 根据提示添加 DNS 记录, 13 | 14 | `API KEYS` 页面创建 `api key` 15 | 16 | 然后执行下面的命令,将 `RESEND_TOKEN` 添加到 secrets 中 17 | 18 | > [!NOTE] 19 | > 如果你觉得麻烦,也可以直接明文放在 `wrangler.toml` 中 `[vars]` 下面,但是不推荐这样做 20 | 21 | 如果你是通过 UI 部署的,可以在 Cloudflare 的 UI 界面中添加到 `Variables and Secrets` 下面 22 | 23 | ```bash 24 | # 切换到 worker 目录 25 | cd worker 26 | wrangler secret put RESEND_TOKEN 27 | ``` 28 | 29 | 如果你有多个域名,对应不同的 `api key`,可以在 `wrangler.toml` 中添加多个 secret, 名称为 `RESEND_TOKEN_` + `<. 换成 _ 的 大写域名>`,例如 30 | 31 | ```bash 32 | wrangler secret put RESEND_TOKEN_XXX_COM 33 | wrangler secret put RESEND_TOKEN_DREAMHUNTER2333_XYZ 34 | ``` 35 | 36 | ## 使用 SMTP 发送邮件 37 | 38 | `SMTP_CONFIG` 的格式如下,key 为域名,value 为 SMTP 配置,SMTP 配置格式详情可以参考 [zou-yu/worker-mailer](https://github.com/zou-yu/worker-mailer/blob/main/README_zh-CN.md) 39 | 40 | ```json 41 | { 42 | "awsl.uk": { 43 | "host": "smtp.xxx.com", 44 | "port": 465, 45 | "secure": true, 46 | "authType": [ 47 | "plain", 48 | "login" 49 | ], 50 | "credentials": { 51 | "username": "username", 52 | "password": "password" 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | 然后执行下面的命令,将 `SMTP_CONFIG` 添加到 secrets 中 59 | 60 | > [!NOTE] 61 | > 如果你觉得麻烦,也可以直接明文放在 `wrangler.toml` 中 `[vars]` 下面,但是不推荐这样做 62 | 63 | 如果你是通过 UI 部署的,可以在 Cloudflare 的 UI 界面中添加到 `Variables and Secrets` 下面 64 | 65 | ```bash 66 | # 切换到 worker 目录 67 | cd worker 68 | wrangler secret put SMTP_CONFIG 69 | ``` 70 | 71 | ## 给 Cloudflare 上已认证的转发邮箱发送邮件 72 | 73 | 仅支持 CLI 部署时使用,在 `wrangler.toml` 中添加 `send_email` 配置 74 | 75 | 发送的目的邮箱地址必须是 Cloudflare 上已认证的邮箱地址,局限性较大,如果需要发送邮件给其他邮箱,可以使用 `resend` 或者 `smtp` 发送邮件 76 | 77 | ```toml 78 | # 通过 Cloudflare 发送邮件 79 | send_email = [ 80 | { name = "SEND_MAIL" }, 81 | ] 82 | ``` 83 | 84 | admin 后台 账号配置 `已验证地址列表(可通过 cf 内部 api 发送邮件)` 85 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import { VitePWA } from 'vite-plugin-pwa' 6 | import AutoImport from 'unplugin-auto-import/vite' 7 | import Components from 'unplugin-vue-components/vite' 8 | import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' 9 | import wasm from "vite-plugin-wasm"; 10 | import topLevelAwait from "vite-plugin-top-level-await"; 11 | 12 | // https://vitejs.dev/config/ 13 | export default defineConfig({ 14 | build: { 15 | outDir: './dist', 16 | }, 17 | plugins: [ 18 | vue(), 19 | wasm(), 20 | topLevelAwait(), 21 | AutoImport({ 22 | imports: [ 23 | 'vue', 24 | { 25 | 'naive-ui': [ 26 | 'useMessage', 27 | 'useNotification', 28 | 'NButton', 29 | 'NPopconfirm', 30 | 'NIcon', 31 | ] 32 | } 33 | ] 34 | }), 35 | Components({ 36 | resolvers: [NaiveUiResolver()] 37 | }), 38 | VitePWA({ 39 | registerType: null, 40 | devOptions: { 41 | enabled: false 42 | }, 43 | workbox: { 44 | disableDevLogs: true, 45 | globPatterns: [], 46 | runtimeCaching: [], 47 | navigateFallback: null, 48 | cleanupOutdatedCaches: true, 49 | }, 50 | manifest: { 51 | name: 'Temp Email', 52 | short_name: 'Temp Email', 53 | description: 'Temp Email - Temporary Email', 54 | theme_color: '#ffffff', 55 | icons: [ 56 | { 57 | src: '/logo.png', 58 | sizes: '192x192', 59 | type: 'image/png' 60 | } 61 | ] 62 | } 63 | }) 64 | ], 65 | resolve: { 66 | alias: { 67 | '@': fileURLToPath(new URL('./src', import.meta.url)) 68 | } 69 | }, 70 | define: { 71 | 'import.meta.env.PACKAGE_VERSION': JSON.stringify(process.env.npm_package_version), 72 | }, 73 | esbuild: { 74 | supported: { 75 | 'top-level-await': true 76 | }, 77 | } 78 | }) 79 | -------------------------------------------------------------------------------- /.github/config/mail-parser-wasm-worker.patch: -------------------------------------------------------------------------------- 1 | diff --git a/worker/src/common.ts b/worker/src/common.ts 2 | index bd9bcc9..e7e2748 100644 3 | --- a/worker/src/common.ts 4 | +++ b/worker/src/common.ts 5 | @@ -273,23 +273,23 @@ export const commonParseMail = async (parsedEmailContext: ParsedEmailContext): P 6 | } 7 | const raw_mail = parsedEmailContext.rawEmail; 8 | // TODO: WASM parse email 9 | - // try { 10 | - // const { parse_message_wrapper } = await import('mail-parser-wasm-worker'); 11 | + try { 12 | + const { parse_message_wrapper } = await import('mail-parser-wasm-worker'); 13 | 14 | - // const parsedEmail = parse_message_wrapper(raw_mail); 15 | - // parsedEmailContext.parsedEmail = { 16 | - // sender: parsedEmail.sender || "", 17 | - // subject: parsedEmail.subject || "", 18 | - // text: parsedEmail.text || "", 19 | - // headers: parsedEmail.headers?.map( 20 | - // (header) => ({ key: header.key, value: header.value }) 21 | - // ) || [], 22 | - // html: parsedEmail.body_html || "", 23 | - // }; 24 | - // return parsedEmailContext.parsedEmail; 25 | - // } catch (e) { 26 | - // console.error("Failed use mail-parser-wasm-worker to parse email", e); 27 | - // } 28 | + const parsedEmail = parse_message_wrapper(raw_mail); 29 | + parsedEmailContext.parsedEmail = { 30 | + sender: parsedEmail.sender || "", 31 | + subject: parsedEmail.subject || "", 32 | + text: parsedEmail.text || "", 33 | + headers: parsedEmail.headers?.map( 34 | + (header) => ({ key: header.key, value: header.value }) 35 | + ) || [], 36 | + html: parsedEmail.body_html || "", 37 | + }; 38 | + return parsedEmailContext.parsedEmail; 39 | + } catch (e) { 40 | + console.error("Failed use mail-parser-wasm-worker to parse email", e); 41 | + } 42 | try { 43 | const { default: PostalMime } = await import('postal-mime'); 44 | const parsedEmail = await PostalMime.parse(raw_mail); 45 | -------------------------------------------------------------------------------- /worker/src/admin_api/mail_webhook_settings.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { CONSTANTS } from "../constants"; 3 | import { WebhookSettings } from "../models"; 4 | import { commonParseMail, sendWebhook } from "../common"; 5 | 6 | async function getWebhookSettings(c: Context): Promise { 7 | const settings = await c.env.KV.get( 8 | CONSTANTS.WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY, "json" 9 | ) || new WebhookSettings(); 10 | return c.json(settings); 11 | } 12 | 13 | async function saveWebhookSettings(c: Context): Promise { 14 | const settings = await c.req.json(); 15 | await c.env.KV.put( 16 | CONSTANTS.WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY, 17 | JSON.stringify(settings)); 18 | return c.json({ success: true }) 19 | } 20 | 21 | async function testWebhookSettings(c: Context): Promise { 22 | const settings = await c.req.json(); 23 | // random raw email 24 | const { id: mailId, raw } = await c.env.DB.prepare( 25 | `SELECT id, raw FROM raw_mails ORDER BY RANDOM() LIMIT 1` 26 | ).first<{ id: string, raw: string }>() || {}; 27 | const parsedEmailContext: ParsedEmailContext = { rawEmail: raw || "" }; 28 | const parsedEmail = await commonParseMail(parsedEmailContext); 29 | const res = await sendWebhook(settings, { 30 | id: mailId || "0", 31 | url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "", 32 | from: parsedEmail?.sender || "test@test.com", 33 | to: "admin@test.com", 34 | subject: parsedEmail?.subject || "test subject", 35 | raw: raw || "test raw email", 36 | parsedText: parsedEmail?.text || "test parsed text", 37 | parsedHtml: parsedEmail?.html || "test parsed html" 38 | }); 39 | if (!res.success) { 40 | return c.text(res.message || "send webhook error", 400); 41 | } 42 | return c.json({ success: true }); 43 | } 44 | 45 | export default { 46 | getWebhookSettings, 47 | saveWebhookSettings, 48 | testWebhookSettings, 49 | } 50 | -------------------------------------------------------------------------------- /worker/src/user_api/user_mail_api.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { handleListQuery } from "../common"; 3 | import UserBindAddressModule from "./bind_address"; 4 | 5 | export default { 6 | getMails: async (c: Context) => { 7 | const { user_id } = c.get("userPayload"); 8 | const { address, limit, offset, keyword } = c.req.query(); 9 | const bindedAddressList = await UserBindAddressModule.getBindedAddressListById(c, user_id); 10 | const addressList = address ? bindedAddressList.filter((item) => item == address) : bindedAddressList; 11 | const addressQuery = `address IN (${addressList.map(() => "?").join(",")})`; 12 | const addressParams = addressList; 13 | const keywordQuery = keyword ? `raw like ?` : ""; 14 | const keywordParams = keyword ? [`%${keyword}%`] : []; 15 | 16 | // user must have at least one binded address to query mails 17 | if (addressList.length <= 0) { 18 | return c.json({ results: [], count: 0 }); 19 | } 20 | 21 | const filterQuerys = [addressQuery, keywordQuery].filter((item) => item).join(" and "); 22 | const finalQuery = filterQuerys.length > 0 ? `where ${filterQuerys}` : ""; 23 | const filterParams = [...addressParams, ...keywordParams] 24 | return await handleListQuery(c, 25 | `SELECT * FROM raw_mails ${finalQuery}`, 26 | `SELECT count(*) as count FROM raw_mails ${finalQuery}`, 27 | filterParams, limit, offset 28 | ); 29 | }, 30 | deleteMail: async (c: Context) => { 31 | const { id } = c.req.param(); 32 | const { user_id } = c.get("userPayload"); 33 | const bindedAddressList = await UserBindAddressModule.getBindedAddressListById(c, user_id); 34 | const { success } = await c.env.DB.prepare( 35 | `DELETE FROM raw_mails WHERE id = ?` 36 | + ` and address IN (${bindedAddressList.map(() => "?").join(",")})` 37 | ).bind(id, ...bindedAddressList).run(); 38 | return c.json({ 39 | success: success 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /worker/src/i18n/zh.ts: -------------------------------------------------------------------------------- 1 | import { LocaleMessages } from "./type"; 2 | 3 | const messages: LocaleMessages = { 4 | CustomAuthPasswordMsg: "你已启用私有站点密码,请提供密码", 5 | UserTokenExpiredMsg: "您的令牌已过期, 请重新登录", 6 | UserAcceesTokenExpiredMsg: "您的访问令牌已过期, 请刷新页面", 7 | UserRoleIsNotAdminMsg: "您的用户角色不是管理员, 无权访问", 8 | NeedAdminPasswordMsg: "您需要提供管理员密码才能访问此页面", 9 | 10 | KVNotAvailableMsg: "KV 不可用, 请联系管理员", 11 | DBNotAvailableMsg: "DB 不可用, 请联系管理员", 12 | JWTSecretNotSetMsg: "JWT_SECRET 未设置, 请联系管理员", 13 | WebhookNotEnabledMsg: "Webhook 未启用, 请联系管理员", 14 | DomainsNotSetMsg: "域名列表未设置, 请联系管理员", 15 | 16 | TurnstileCheckFailedMsg: "人机验证检查失败", 17 | NewAddressDisabledMsg: "新建邮箱地址已禁用, 请联系管理员", 18 | NewAddressAnonymousDisabledMsg: "匿名用户新建邮箱地址已禁用, 请联系管理员", 19 | FailedCreateAddressMsg: "创建邮箱地址失败", 20 | InvalidAddressMsg: "无效的邮箱地址", 21 | InvalidAddressCredentialMsg: "无效的邮箱地址凭据", 22 | UserDeleteEmailDisabledMsg: "用户删除邮箱/邮件已禁用, 请联系管理员", 23 | 24 | UserNotFoundMsg: "用户不存在", 25 | UserAlreadyExistsMsg: "用户已存在, 请登录", 26 | FailedToRegisterMsg: "注册失败", 27 | UserRegistrationDisabledMsg: "用户注册已禁用, 请联系管理员", 28 | UserMailDomainMustInMsg: "用户邮箱域必须在此列表中", 29 | InvalidVerifyCodeMsg: "无效的验证码", 30 | InvalidEmailOrPasswordMsg: "无效的邮箱或密码", 31 | VerifyMailSenderNotSetMsg: "验证邮件发送邮箱未设置, 请联系管理员", 32 | CodeAlreadySentMsg: "验证码已发送, 请稍等", 33 | InvalidUserDefaultRoleMsg: "无效的用户默认角色, 请联系管理员", 34 | FailedUpdateUserDefaultRoleMsg: "更新用户默认角色失败, 请联系管理员", 35 | 36 | Oauth2ClientIDNotFoundMsg: "Oauth2 客户端 ID 未设置, 请联系管理员", 37 | Oauth2CliendIDOrCodeMissingMsg: "Oauth2 客户端 ID 或 code 缺失", 38 | Oauth2FailedGetUserInfoMsg: "从 Oauth2 提供商获取用户信息失败", 39 | Oauth2FailedGetAccessTokenMsg: "从 Oauth2 提供商获取访问令牌失败", 40 | Oauth2FailedGetUserEmailMsg: "从 Oauth2 提供商获取用户邮箱失败", 41 | 42 | PasswordChangeDisabledMsg: "密码修改已禁用", 43 | NewPasswordRequiredMsg: "新密码不能为空", 44 | InvalidAddressTokenMsg: "无效的地址令牌", 45 | FailedUpdatePasswordMsg: "更新密码失败", 46 | PasswordLoginDisabledMsg: "密码登录已禁用", 47 | EmailPasswordRequiredMsg: "邮箱和密码不能为空", 48 | AddressNotFoundMsg: "邮箱地址不存在", 49 | } 50 | 51 | export default messages; 52 | -------------------------------------------------------------------------------- /worker/src/mails_api/auto_reply.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { getBooleanValue } from "../utils"; 3 | 4 | 5 | export default { 6 | getAutoReply: async (c: Context) => { 7 | if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) { 8 | return c.text("Auto reply is disabled", 403) 9 | } 10 | const { address } = c.get("jwtPayload") 11 | const results = await c.env.DB.prepare( 12 | `SELECT * FROM auto_reply_mails where address = ? ` 13 | ).bind(address).first(); 14 | if (!results) { 15 | return c.json({}); 16 | } 17 | return c.json({ 18 | subject: results.subject, 19 | message: results.message, 20 | enabled: results.enabled == 1, 21 | source_prefix: results.source_prefix, 22 | name: results.name, 23 | }) 24 | }, 25 | saveAutoReply: async (c: Context) => { 26 | if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) { 27 | return c.text("Auto reply is disabled", 403) 28 | } 29 | const { address } = c.get("jwtPayload"); 30 | const { auto_reply } = await c.req.json(); 31 | const { name, subject, source_prefix, message, enabled } = auto_reply; 32 | if ((!subject || !message) && enabled) { 33 | return c.text("Invalid subject or message", 400) 34 | } 35 | else if (subject.length > 255 || message.length > 255) { 36 | return c.text("Subject or message too long", 400) 37 | } 38 | const { success } = await c.env.DB.prepare( 39 | `INSERT OR REPLACE INTO auto_reply_mails` 40 | + ` (name, address, source_prefix, subject, message, enabled)` 41 | + ` VALUES (?, ?, ?, ?, ?, ?)` 42 | ).bind( 43 | name || '', address, source_prefix || '', 44 | subject || '', message || '', enabled ? 1 : 0 45 | ).run(); 46 | if (!success) { 47 | return c.text("Failed to auto_reply settings", 500) 48 | } 49 | return c.json({ 50 | success: success 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/tag_build.yml: -------------------------------------------------------------------------------- 1 | name: Tag Build CI 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Install Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | 22 | - uses: pnpm/action-setup@v3 23 | name: Install pnpm 24 | id: pnpm-install 25 | with: 26 | version: 8 27 | run_install: false 28 | 29 | - name: Build Frontend 30 | run: cd frontend && pnpm install --no-frozen-lockfile && pnpm build:release 31 | 32 | - name: Zip Frontend dist 33 | run: cd frontend/dist/ && zip -r frontend.zip * && mv frontend.zip ../ 34 | 35 | - name: Build Telegram Frontend 36 | run: cd frontend && pnpm install --no-frozen-lockfile && pnpm build:telegram:release 37 | 38 | - name: Zip Telegram Frontend dist 39 | run: cd frontend/dist/ && zip -r telegram-frontend.zip * && mv telegram-frontend.zip ../ 40 | 41 | - name: cp wrangler.toml 42 | run: cd worker && cp wrangler.toml.template wrangler.toml 43 | 44 | - name: Build Backend 45 | run: cd worker && pnpm install --no-frozen-lockfile && pnpm build 46 | 47 | - name: Move worker.js 48 | run: cd worker/dist && mv worker.js ../ 49 | 50 | - name: Build Worker with wasm mail parser 51 | run: | 52 | cd worker 53 | echo "Using mail-parser-wasm-worker" 54 | pnpm add mail-parser-wasm-worker 55 | git apply ../.github/config/mail-parser-wasm-worker.patch 56 | echo "Applied mail-parser-wasm-worker patch" 57 | pnpm build 58 | zip -r worker-with-wasm-mail-parser.zip dist/worker.js dist/*.wasm 59 | 60 | - name: Upload to Release 61 | uses: softprops/action-gh-release@v2 62 | with: 63 | files: | 64 | frontend/frontend.zip 65 | frontend/telegram-frontend.zip 66 | worker/worker.js 67 | worker/worker-with-wasm-mail-parser.zip 68 | -------------------------------------------------------------------------------- /.github/workflows/docs_deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | 3 | on: 4 | push: 5 | paths: 6 | - "vitepress-docs/**" 7 | tags: 8 | - "*" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Install Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 20 26 | 27 | - uses: pnpm/action-setup@v3 28 | name: Install pnpm 29 | id: pnpm-install 30 | with: 31 | version: 8 32 | run_install: false 33 | 34 | - name: check github release done 35 | run: | 36 | for ((attempt=1; attempt<=10; attempt++)); do 37 | if wget -q --spider "https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/frontend.zip"; then 38 | echo "frontend.zip found." 39 | break 40 | else 41 | if [ $attempt -eq 10 ]; then 42 | echo "Exceeded maximum retries. frontend.zip not found." 43 | else 44 | echo "frontend.zip not found. Retrying in 30 seconds..." 45 | sleep 30 46 | fi 47 | fi 48 | done 49 | 50 | - name: Deploy Docs for ${{github.ref_name}} 51 | run: | 52 | cd vitepress-docs/ 53 | wget https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/frontend.zip -O docs/public/ui_install/frontend.zip 54 | pnpm install --no-frozen-lockfile 55 | if [[ ${{github.ref}} == refs/tags/* ]]; then 56 | export TAG_NAME=${{github.ref_name}} 57 | else 58 | export TAG_NAME=$(git describe --tags --abbrev=0) 59 | fi 60 | echo "Deploying docs for tag $TAG_NAME" 61 | pnpm run deploy 62 | env: 63 | CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 64 | CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} 65 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/send-mail-api.md: -------------------------------------------------------------------------------- 1 | # Send Email API 2 | 3 | ## Send Email via HTTP API 4 | 5 | This is a `python` example using the `requests` library to send emails. 6 | 7 | ```python 8 | send_body = { 9 | "from_name": "Sender Name", 10 | "to_name": "Recipient Name", 11 | "to_mail": "Recipient Address", 12 | "subject": "Email Subject", 13 | "is_html": False, # Set whether it's HTML based on content 14 | "content": "", 15 | } 16 | 17 | res = requests.post( 18 | "http://localhost:8787/api/send_mail", 19 | json=send_body, headers={ 20 | "Authorization": f"Bearer {your_JWT_password}", 21 | # "x-custom-auth": "", # If custom password is enabled 22 | "Content-Type": "application/json" 23 | } 24 | ) 25 | 26 | # Using body authentication 27 | send_body = { 28 | "token": "", 29 | "from_name": "Sender Name", 30 | "to_name": "Recipient Name", 31 | "to_mail": "Recipient Address", 32 | "subject": "Email Subject", 33 | "is_html": False, # Set whether it's HTML based on content 34 | "content": "", 35 | } 36 | res = requests.post( 37 | "http://localhost:8787/external/api/send_mail", 38 | json=send_body, headers={ 39 | "Content-Type": "application/json" 40 | } 41 | ) 42 | ``` 43 | 44 | ## Send Email via SMTP 45 | 46 | Please first refer to [Configure SMTP Proxy](/en/guide/feature/config-smtp-proxy.html). 47 | 48 | This is a `python` example using the `smtplib` library to send emails. 49 | 50 | `JWT Token Password`: This is the email login password, which can be viewed in the password menu in the UI interface. 51 | 52 | ```python 53 | import smtplib 54 | 55 | from email.mime.text import MIMEText 56 | from email.mime.multipart import MIMEMultipart 57 | 58 | 59 | with smtplib.SMTP('localhost', 8025) as smtp: 60 | smtp.login("jwt", "Enter your JWT token password here") 61 | message = MIMEMultipart() 62 | message['From'] = "Me " 63 | message['To'] = "Admin " 64 | message['Subject'] = "Test Subject" 65 | message.attach(MIMEText("Test Content", 'html')) 66 | smtp.sendmail("me@awsl.uk", "admin@awsl.uk", message.as_string()) 67 | ``` 68 | -------------------------------------------------------------------------------- /frontend/src/views/admin/Mails.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 65 | -------------------------------------------------------------------------------- /frontend/src/views/admin/UserAddressManagement.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 89 | 90 | 95 | -------------------------------------------------------------------------------- /frontend/src/views/telegram/Mail.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 67 | 68 | 69 | 78 | -------------------------------------------------------------------------------- /frontend/src/components/ShadowHtmlComponent.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 76 | -------------------------------------------------------------------------------- /frontend/src/views/user/UserBar.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 61 | 62 | 77 | -------------------------------------------------------------------------------- /.github/workflows/frontend_deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Frontend 2 | 3 | on: 4 | workflow_run: 5 | workflows: [Upstream Sync] 6 | types: [completed] 7 | push: 8 | tags: 9 | - "*" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Install Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 20 25 | 26 | - uses: pnpm/action-setup@v3 27 | name: Install pnpm 28 | id: pnpm-install 29 | with: 30 | version: 8 31 | run_install: false 32 | 33 | - name: Deploy Frontend for ${{ github.ref_name }} 34 | run: | 35 | cd frontend/ 36 | echo "${{ secrets.FRONTEND_ENV }}" > .env.prod 37 | export project_name=${{ secrets.FRONTEND_NAME }} 38 | pnpm install --no-frozen-lockfile 39 | export frontend_branch=${{ secrets.FRONTEND_BRANCH }} 40 | if [ -n "$frontend_branch" ]; then 41 | echo "Deploying branch $frontend_branch" 42 | pnpm run deploy:actions --project-name=$project_name --branch $frontend_branch 43 | else 44 | echo "Deploying branch production" 45 | pnpm run deploy --project-name=$project_name 46 | fi 47 | echo "Deploying production for ${{ github.ref_name }}" 48 | echo "Deployed for tag ${{ github.ref_name }}" 49 | 50 | export tg_mini_app_project_name=${{ secrets.TG_FRONTEND_NAME }} 51 | if [ -n "$tg_mini_app_project_name" ]; then 52 | echo "Deploying telegram mini app $tg_mini_app_project_name" 53 | if [ -n "$frontend_branch" ]; then 54 | echo "Deploying telegram mini app branch $frontend_branch" 55 | pnpm run deploy:actions:telegram --project-name=$tg_mini_app_project_name --branch $frontend_branch 56 | else 57 | echo "Deploying telegram mini app branch production" 58 | pnpm run deploy:telegram --project-name=$tg_mini_app_project_name 59 | fi 60 | echo "Deployed telegram mini app for ${{ github.ref_name }}" 61 | fi 62 | env: 63 | CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 64 | CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} 65 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudflare_temp_email", 3 | "version": "1.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build -m prod --emptyOutDir", 9 | "build:release": "vite build -m example --emptyOutDir", 10 | "build:pages": "vite build -m pages --emptyOutDir", 11 | "build:pages:nopwa": "VITE_PWA_DISABLED=true vite build -m pages --emptyOutDir", 12 | "build:telegram": "VITE_IS_TELEGRAM=true vite build -m prod --emptyOutDir", 13 | "build:telegram:pages": "VITE_IS_TELEGRAM=true vite build -m pages --emptyOutDir", 14 | "build:telegram:release": "VITE_IS_TELEGRAM=true vite build -m example --emptyOutDir", 15 | "preview": "vite preview", 16 | "deploy:telegram": "npm run build:telegram && wrangler pages deploy ./dist --branch production", 17 | "deploy:actions:telegram": "npm run build:telegram && wrangler pages deploy ./dist", 18 | "deploy:preview": "npm run build && wrangler pages deploy ./dist --branch preview", 19 | "deploy": "npm run build && wrangler pages deploy ./dist --branch production", 20 | "deploy:actions": "npm run build && wrangler pages deploy ./dist" 21 | }, 22 | "dependencies": { 23 | "@fingerprintjs/fingerprintjs": "^5.0.1", 24 | "@simplewebauthn/browser": "10.0.0", 25 | "@unhead/vue": "^1.11.20", 26 | "@vueuse/core": "^12.8.2", 27 | "@wangeditor/editor": "^5.1.23", 28 | "@wangeditor/editor-for-vue": "^5.1.12", 29 | "axios": "^1.13.2", 30 | "jszip": "^3.10.1", 31 | "mail-parser-wasm": "^0.2.1", 32 | "naive-ui": "^2.43.2", 33 | "postal-mime": "^2.6.1", 34 | "vooks": "^0.2.12", 35 | "vue": "^3.5.25", 36 | "vue-clipboard3": "^2.0.0", 37 | "vue-i18n": "^11.2.2", 38 | "vue-router": "^4.6.3" 39 | }, 40 | "devDependencies": { 41 | "@vicons/fa": "^0.13.0", 42 | "@vicons/material": "^0.13.0", 43 | "@vitejs/plugin-vue": "^5.2.4", 44 | "unplugin-auto-import": "^19.3.0", 45 | "unplugin-vue-components": "^28.8.0", 46 | "vite": "^6.4.1", 47 | "vite-plugin-pwa": "^1.2.0", 48 | "vite-plugin-top-level-await": "^1.6.0", 49 | "vite-plugin-wasm": "^3.5.0", 50 | "workbox-build": "^7.4.0", 51 | "workbox-window": "^7.4.0", 52 | "wrangler": "^4.53.0" 53 | }, 54 | "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39" 55 | } 56 | -------------------------------------------------------------------------------- /worker/src/commom_api.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono' 2 | 3 | import utils from './utils'; 4 | import { CONSTANTS } from './constants'; 5 | import { isS3Enabled } from './mails_api/s3_attachment'; 6 | 7 | const api = new Hono 8 | 9 | api.get('/open_api/settings', async (c) => { 10 | // check header x-custom-auth 11 | let needAuth = false; 12 | const passwords = utils.getPasswords(c); 13 | if (passwords && passwords.length > 0) { 14 | const auth = c.req.raw.headers.get("x-custom-auth"); 15 | needAuth = !auth || !passwords.includes(auth); 16 | } 17 | 18 | return c.json({ 19 | "title": c.env.TITLE, 20 | "announcement": utils.getStringValue(c.env.ANNOUNCEMENT), 21 | "alwaysShowAnnouncement": utils.getBooleanValue(c.env.ALWAYS_SHOW_ANNOUNCEMENT), 22 | "prefix": utils.getStringValue(c.env.PREFIX), 23 | "addressRegex": utils.getStringValue(c.env.ADDRESS_REGEX), 24 | "minAddressLen": utils.getIntValue(c.env.MIN_ADDRESS_LEN, 1), 25 | "maxAddressLen": utils.getIntValue(c.env.MAX_ADDRESS_LEN, 30), 26 | "defaultDomains": utils.getDefaultDomains(c), 27 | "domains": utils.getDomains(c), 28 | "domainLabels": utils.getStringArray(c.env.DOMAIN_LABELS), 29 | "needAuth": needAuth, 30 | "adminContact": c.env.ADMIN_CONTACT, 31 | "enableUserCreateEmail": utils.getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL), 32 | "disableAnonymousUserCreateEmail": utils.getBooleanValue(c.env.DISABLE_ANONYMOUS_USER_CREATE_EMAIL), 33 | "disableCustomAddressName": utils.getBooleanValue(c.env.DISABLE_CUSTOM_ADDRESS_NAME), 34 | "enableUserDeleteEmail": utils.getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL), 35 | "enableAutoReply": utils.getBooleanValue(c.env.ENABLE_AUTO_REPLY), 36 | "enableIndexAbout": utils.getBooleanValue(c.env.ENABLE_INDEX_ABOUT), 37 | "copyright": c.env.COPYRIGHT, 38 | "cfTurnstileSiteKey": c.env.CF_TURNSTILE_SITE_KEY, 39 | "enableWebhook": utils.getBooleanValue(c.env.ENABLE_WEBHOOK), 40 | "isS3Enabled": isS3Enabled(c), 41 | "version": CONSTANTS.VERSION, 42 | "showGithub": !utils.getBooleanValue(c.env.DISABLE_SHOW_GITHUB), 43 | "disableAdminPasswordCheck": utils.getBooleanValue(c.env.DISABLE_ADMIN_PASSWORD_CHECK), 44 | "enableAddressPassword": utils.getBooleanValue(c.env.ENABLE_ADDRESS_PASSWORD) 45 | }); 46 | }) 47 | 48 | export { api } 49 | -------------------------------------------------------------------------------- /worker/src/email/check_junk.ts: -------------------------------------------------------------------------------- 1 | import { getBooleanValue, getStringArray } from "../utils"; 2 | import { commonParseMail } from "../common"; 3 | 4 | export const check_if_junk_mail = async ( 5 | env: Bindings, address: string, 6 | parsedEmailContext: ParsedEmailContext, 7 | message_id: string | null 8 | ): Promise => { 9 | if (!getBooleanValue(env.ENABLE_CHECK_JUNK_MAIL)) { 10 | return false; 11 | } 12 | const parsedEmail = await commonParseMail(parsedEmailContext); 13 | if (!parsedEmail?.headers) return false; 14 | 15 | const checkListWhenExist = getStringArray(env.JUNK_MAIL_CHECK_LIST); 16 | const forcePassList = getStringArray(env.JUNK_MAIL_FORCE_PASS_LIST); 17 | const passedList: string[] = []; 18 | const existList: string[] = []; 19 | 20 | const headers = parsedEmail.headers; 21 | for (const header of headers) { 22 | if (!header["key"]) continue; 23 | if (!header["value"]) continue; 24 | 25 | // check spf 26 | if (header["key"].toLowerCase() == "received-spf") { 27 | existList.push("spf"); 28 | if (header["value"].toLowerCase().includes("pass")) { 29 | passedList.push("spf"); 30 | } 31 | } 32 | 33 | // check dkim and dmarc 34 | if (header["key"].toLowerCase() == "authentication-results") { 35 | if (header["value"].toLowerCase().includes("dkim=")) { 36 | existList.push("dkim"); 37 | if (header["value"].toLowerCase().includes("dkim=pass")) { 38 | passedList.push("dkim"); 39 | } 40 | } 41 | if (header["value"].toLowerCase().includes("dmarc=")) { 42 | existList.push("dmarc"); 43 | if (header["value"].toLowerCase().includes("dmarc=pass")) { 44 | passedList.push("dmarc"); 45 | } 46 | } 47 | } 48 | } 49 | // check if all checkListWhenExist item passed when exist 50 | if (checkListWhenExist?.some( 51 | (checkName) => existList.includes(checkName.toLowerCase()) 52 | && !passedList.includes(checkName.toLowerCase()) 53 | )) { 54 | return true; 55 | } 56 | 57 | if (forcePassList?.length == 0) return false; 58 | 59 | // check force pass list 60 | return forcePassList.some( 61 | (checkName) => !passedList.includes(checkName.toLowerCase()) 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/ai-extract.md: -------------------------------------------------------------------------------- 1 | # AI 邮件识别 2 | 3 | > [!NOTE] 4 | > 此功能从 v1.1.0 版本开始支持 5 | > 6 | > 本功能参考自 [Alle 项目](https://github.com/bestruirui/Alle/blob/62e74629ded0c7966c12d4e1c54f0bcc2e54f12c/src/lib/email/extract.ts#L54) 7 | 8 | ## 功能说明 9 | 10 | AI 邮件识别功能使用 Cloudflare Workers AI 自动分析收到的邮件内容,智能提取重要信息,包括: 11 | 12 | - **验证码** (auth_code) - OTP、安全码、确认码等 13 | - **认证链接** (auth_link) - 登录、验证、激活、重置密码链接 14 | - **服务链接** (service_link) - GitHub、GitLab、部署通知等服务相关链接 15 | - **订阅管理链接** (subscription_link) - 退订、管理订阅等链接 16 | - **其他链接** (other_link) - 其他有价值的链接 17 | 18 | 提取结果会自动保存到数据库的 `metadata` 字段中,前端可以直接展示提取的验证码或链接。 19 | 20 | ## 配置变量 21 | 22 | | 变量名 | 类型 | 说明 | 示例 | 23 | | ------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- | 24 | | `ENABLE_AI_EMAIL_EXTRACT` | 文本/JSON | 是否启用 AI 邮件识别功能 | `true` | 25 | | `AI_EXTRACT_MODEL` | 文本 | AI 模型名称,从[支持 JSON 模式的模型](https://developers.cloudflare.com/workers-ai/features/json-mode/#supported-models)中选择 | `@cf/meta/llama-3.1-8b-instruct` | 26 | 27 | ## Workers AI 绑定 28 | 29 | 需要在 `wrangler.toml` 中配置 Workers AI 绑定: 30 | 31 | ```toml 32 | [ai] 33 | binding = "AI" 34 | ``` 35 | 36 | 或在 Cloudflare Dashboard 的 Worker 设置中添加: 37 | - **Variable name**: `AI` 38 | - **Type**: Workers AI 39 | 40 | ## 地址白名单(可选) 41 | 42 | 为了控制成本和资源使用,可以在 Admin 控制台的 **AI 提取设置** 页面配置地址白名单: 43 | 44 | ### 配置说明 45 | 46 | - **未启用白名单**:所有邮箱地址都可使用 AI 提取功能 47 | - **启用白名单**:仅白名单中的邮箱地址会进行 AI 提取 48 | 49 | ### 白名单格式 50 | 51 | 每行一个地址,支持通配符 `*` 匹配任意字符: 52 | 53 | - **精确匹配**:`user@example.com` - 仅匹配该邮箱 54 | - **域名通配符**:`*@example.com` - 匹配 example.com 域名下的所有邮箱 55 | - **用户通配符**:`admin*@example.com` - 匹配 admin 开头的邮箱 56 | - **任意位置通配符**:`*test*@example.com` - 匹配包含 test 的邮箱 57 | - **多个通配符**:`admin*@*.com` - 匹配所有 .com 域名下 admin 开头的邮箱 58 | 59 | ### 配置示例 60 | 61 | ```text 62 | user@example.com 63 | *@mydomain.com 64 | admin*@company.com 65 | ``` 66 | 67 | 此配置将只对以下邮箱进行 AI 提取: 68 | - `user@example.com`(精确匹配) 69 | - 所有 `@mydomain.com` 的邮箱(如 `test@mydomain.com`、`admin@mydomain.com`) 70 | - 所有 `admin` 开头的 `@company.com` 邮箱(如 `admin@company.com`、`admin123@company.com`) 71 | -------------------------------------------------------------------------------- /worker/src/scheduled.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'hono'; 2 | import { cleanup } from './common' 3 | import { CONSTANTS } from './constants' 4 | import { getJsonSetting } from './utils'; 5 | import { CleanupSettings } from './models'; 6 | 7 | export async function scheduled(event: ScheduledEvent, env: Bindings, ctx: any) { 8 | console.log("Scheduled event: ", event); 9 | const autoCleanupSetting = await getJsonSetting( 10 | { env: env, } as Context, 11 | CONSTANTS.AUTO_CLEANUP_KEY 12 | ); 13 | if (!autoCleanupSetting) { 14 | console.log("No auto cleanup settings found, skipping cleanup."); 15 | return; 16 | } 17 | console.log("autoCleanupSetting:", JSON.stringify(autoCleanupSetting)); 18 | if (autoCleanupSetting.enableMailsAutoCleanup) { 19 | await cleanup( 20 | { env: env, } as Context, 21 | "mails", 22 | autoCleanupSetting.cleanMailsDays 23 | ); 24 | } 25 | if (autoCleanupSetting.enableUnknowMailsAutoCleanup) { 26 | await cleanup( 27 | { env: env, } as Context, 28 | "mails_unknow", 29 | autoCleanupSetting.cleanUnknowMailsDays 30 | ); 31 | } 32 | if (autoCleanupSetting.enableSendBoxAutoCleanup) { 33 | await cleanup( 34 | { env: env, } as Context, 35 | "sendbox", 36 | autoCleanupSetting.cleanSendBoxDays 37 | ); 38 | } 39 | if (autoCleanupSetting.enableInactiveAddressAutoCleanup) { 40 | await cleanup( 41 | { env: env, } as Context, 42 | "inactiveAddress", 43 | autoCleanupSetting.cleanInactiveAddressDays 44 | ); 45 | } 46 | if (autoCleanupSetting.enableAddressAutoCleanup) { 47 | await cleanup( 48 | { env: env, } as Context, 49 | "addressCreated", 50 | autoCleanupSetting.cleanAddressDays 51 | ); 52 | } 53 | if (autoCleanupSetting.enableUnboundAddressAutoCleanup) { 54 | await cleanup( 55 | { env: env, } as Context, 56 | "unboundAddress", 57 | autoCleanupSetting.cleanUnboundAddressDays 58 | ); 59 | } 60 | if (autoCleanupSetting.enableEmptyAddressAutoCleanup) { 61 | await cleanup( 62 | { env: env, } as Context, 63 | "emptyAddress", 64 | autoCleanupSetting.cleanEmptyAddressDays 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/backend_deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Backend 2 | 3 | on: 4 | workflow_run: 5 | workflows: [Upstream Sync] 6 | types: [completed] 7 | push: 8 | tags: 9 | - "*" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Install Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 20 25 | 26 | - uses: pnpm/action-setup@v3 27 | name: Install pnpm 28 | id: pnpm-install 29 | with: 30 | version: 8 31 | run_install: false 32 | 33 | - name: Deploy Backend for ${{ github.ref_name }} 34 | run: | 35 | export use_worker_assets=${{ secrets.USE_WORKER_ASSETS }} 36 | export use_worker_assets_with_telegram=${{ secrets.USE_WORKER_ASSETS_WITH_TELEGRAM }} 37 | if [ -n "$use_worker_assets" ]; then 38 | cd frontend/ 39 | pnpm install --no-frozen-lockfile 40 | if [ -n "$use_worker_assets_with_telegram" ]; then 41 | echo "Building with telegram pages" 42 | pnpm build:telegram:pages 43 | else 44 | echo "Building with normal pages" 45 | pnpm build:pages 46 | fi 47 | cd .. 48 | fi 49 | 50 | export debug_mode=${{ secrets.DEBUG_MODE }} 51 | export use_mail_wasm_parser=${{ secrets.BACKEND_USE_MAIL_WASM_PARSER }} 52 | cd worker/ 53 | echo '${{ secrets.BACKEND_TOML }}' > wrangler.toml 54 | pnpm install --no-frozen-lockfile 55 | 56 | if [ -n "$use_mail_wasm_parser" ]; then 57 | echo "Using mail-parser-wasm-worker" 58 | pnpm add mail-parser-wasm-worker 59 | git apply ../.github/config/mail-parser-wasm-worker.patch 60 | echo "Applied mail-parser-wasm-worker patch" 61 | fi 62 | 63 | if [ "$debug_mode" = "true" ]; then 64 | pnpm run deploy 65 | else 66 | output=$(pnpm run deploy 2>&1) 67 | if [ $? -ne 0 ]; then 68 | code=$? 69 | echo "Command failed with exit code $code" 70 | exit $code 71 | fi 72 | fi 73 | echo "Deployed for tag ${{ github.ref_name }}" 74 | env: 75 | CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 76 | CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} 77 | -------------------------------------------------------------------------------- /smtp_proxy_server/parse_email.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import logging 4 | import email 5 | 6 | from email.message import Message 7 | from email.mime.multipart import MIMEMultipart 8 | from email.mime.text import MIMEText 9 | 10 | 11 | from models import EmailModel 12 | 13 | _logger = logging.getLogger(__name__) 14 | _logger.setLevel(logging.INFO) 15 | 16 | 17 | def get_email_model(msg: Message): 18 | subparts = [ 19 | get_email_model(subpart) 20 | for subpart in msg.get_payload() 21 | ] if msg.is_multipart() else [] 22 | body = "" if msg.is_multipart() else msg._payload 23 | return EmailModel( 24 | headers={k: v for k, v in msg.items()}, 25 | body=body, 26 | content_type=msg.get_content_type(), 27 | size=len(body) + sum(subpart.size for subpart in subparts), 28 | subparts=subparts, 29 | ) 30 | 31 | 32 | def parse_email(raw: str) -> EmailModel: 33 | try: 34 | msg = email.message_from_string(raw) 35 | return get_email_model(msg) 36 | except Exception as e: 37 | _logger.error(f"Could not parse email: {e}") 38 | return EmailModel( 39 | headers={}, 40 | body="could not parse email", 41 | content_type="text/plain", 42 | size=len("could not parse email"), 43 | subparts=[], 44 | ) 45 | 46 | 47 | def generate_email_model(item: dict) -> EmailModel: 48 | email_json = json.loads(item["raw"]) 49 | message = MIMEMultipart() 50 | if email_json.get("version") == "v2": 51 | message['From'] = f'{email_json["from_name"]} <{item["address"]}>' if email_json.get("from_name") else item["address"] 52 | message['To'] = f'{email_json["to_name"]} <{email_json["to_mail"]}>' if email_json.get("to_name") else email_json["to_mail"] 53 | message.attach(MIMEText( 54 | email_json["content"], 55 | "html" if email_json.get("is_html") else "plain" 56 | )) 57 | else: 58 | message['From'] = f'{email_json["from"]["name"]} <{email_json["from"]["email"]}>' 59 | message['To'] = ", ".join( 60 | [f"{to['name']} <{to['email']}>" for to in email_json["personalizations"][0]["to"]]) 61 | message.attach(MIMEText( 62 | email_json["content"][0]["value"], 63 | "html" if "html" in email_json["content"][0]["type"] else "plain" 64 | )) 65 | message['Subject'] = email_json["subject"] 66 | message["Date"] = datetime.datetime.strptime( 67 | item["created_at"], "%Y-%m-%d %H:%M:%S" 68 | ).strftime("%a, %d %b %Y %H:%M:%S +0000") 69 | return parse_email(message.as_string()) 70 | -------------------------------------------------------------------------------- /worker/src/mails_api/address_auth.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'hono'; 2 | import i18n from '../i18n'; 3 | import { getBooleanValue, hashPassword } from '../utils'; 4 | import { Jwt } from 'hono/utils/jwt'; 5 | 6 | export default { 7 | // 修改地址密码 8 | changePassword: async (c: Context) => { 9 | const { new_password } = await c.req.json(); 10 | const lang = c.get("lang") || c.env.DEFAULT_LANG; 11 | const msgs = i18n.getMessages(lang); 12 | const { address, address_id } = c.get("jwtPayload"); 13 | 14 | // 检查功能是否启用 15 | if (!getBooleanValue(c.env.ENABLE_ADDRESS_PASSWORD)) { 16 | return c.text(msgs.PasswordChangeDisabledMsg, 403); 17 | } 18 | 19 | if (!new_password) { 20 | return c.text(msgs.NewPasswordRequiredMsg, 400); 21 | } 22 | 23 | if (!address || !address_id) { 24 | return c.text(msgs.InvalidAddressTokenMsg, 400); 25 | } 26 | 27 | // 更新密码 28 | const { success } = await c.env.DB.prepare( 29 | `UPDATE address SET password = ?, updated_at = datetime('now') WHERE id = ?` 30 | ).bind(new_password, address_id).run(); 31 | 32 | if (!success) { 33 | return c.text(msgs.FailedUpdatePasswordMsg, 500); 34 | } 35 | 36 | return c.json({ success: true }); 37 | }, 38 | 39 | // 地址密码登录 40 | login: async (c: Context) => { 41 | const { email, password, cf_token } = await c.req.json(); 42 | const lang = c.get("lang") || c.env.DEFAULT_LANG; 43 | const msgs = i18n.getMessages(lang); 44 | 45 | // 检查功能是否启用 46 | if (!getBooleanValue(c.env.ENABLE_ADDRESS_PASSWORD)) { 47 | return c.text(msgs.PasswordLoginDisabledMsg, 403); 48 | } 49 | 50 | if (!email || !password) { 51 | return c.text(msgs.EmailPasswordRequiredMsg, 400); 52 | } 53 | 54 | // 查找地址 55 | const address = await c.env.DB.prepare( 56 | `SELECT * FROM address WHERE name = ?` 57 | ).bind(email).first(); 58 | 59 | if (!address) { 60 | return c.text(msgs.AddressNotFoundMsg, 404); 61 | } 62 | 63 | // 验证密码 64 | if (address.password !== password) { 65 | return c.text(msgs.InvalidEmailOrPasswordMsg, 401); 66 | } 67 | 68 | // 创建JWT 69 | const jwt = await Jwt.sign({ 70 | address: address.name, 71 | address_id: address.id 72 | }, c.env.JWT_SECRET, "HS256"); 73 | 74 | return c.json({ 75 | jwt: jwt, 76 | address: address.name 77 | }); 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/mail_parser_wasm_worker.md: -------------------------------------------------------------------------------- 1 | # mail-parser-wasm-worker 2 | 3 | > [!NOTE] 4 | > 如果你使用了 webhook 转发,或者 telegram bot 接受邮件,但是邮件内容是乱码,或者无法解析,你对解析的需要更高的要求,可以使用这个功能。 5 | 6 | ## UI 部署 7 | 8 | 1. 下载 [worker-with-wasm-mail-parser.zip](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/worker-with-wasm-mail-parser.zip) 9 | 10 | 2. 回到 `Overview`,找到刚刚创建的 worker,点击 `Edit Code`, 删除原来的文件,上传 `worker.js` 和 `wasm` 后缀的文件, 点击 `Deploy` 11 | 12 | > [!NOTE] 13 | > 上传需要先点击左侧菜单的 Explorer, 14 | > 在文件列表的窗口里点击鼠标右键,在右键菜单里找到 `Upload`, 15 | > 请参考下面的截图 16 | > 17 | > 参考: [issues156](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/156#issuecomment-2079453822) 18 | 19 | ![worker2](/ui_install/worker-2.png) 20 | ![worker-upload](/ui_install/worker-upload.png) 21 | 22 | ## CLI 部署 23 | 24 | ### 修改代码 25 | 26 | ```bash 27 | cd worker 28 | pnpm add mail-parser-wasm-worker 29 | ``` 30 | 31 | 编辑 `worker/src/common.ts`, 取消注释这段代码,使用 mail-parser-wasm-worker 来解析邮件 32 | 33 | ```ts 34 | export const commonParseMail = async (raw_mail: string | undefined | null): Promise<{ 35 | sender: string, 36 | subject: string, 37 | text: string, 38 | html: string 39 | } | undefined> => { 40 | if (!raw_mail) { 41 | return undefined; 42 | } 43 | // 取消注释这段代码,使用 mail-parser-wasm-worker 来解析邮件 start 44 | // TODO: WASM parse email 45 | try { 46 | const { parse_message_wrapper } = await import('mail-parser-wasm-worker'); 47 | 48 | const parsedEmail = parse_message_wrapper(raw_mail); 49 | return { 50 | sender: parsedEmail.sender || "", 51 | subject: parsedEmail.subject || "", 52 | text: parsedEmail.text || "", 53 | headers: parsedEmail.headers || [], 54 | html: parsedEmail.body_html || "", 55 | }; 56 | } catch (e) { 57 | console.error("Failed use mail-parser-wasm-worker to parse email", e); 58 | } 59 | // 取消注释这段代码,使用 mail-parser-wasm-worker 来解析邮件 end 60 | try { 61 | const { default: PostalMime } = await import('postal-mime'); 62 | const parsedEmail = await PostalMime.parse(raw_mail); 63 | return { 64 | sender: parsedEmail.from ? `${parsedEmail.from.name} <${parsedEmail.from.address}>` : "", 65 | subject: parsedEmail.subject || "", 66 | text: parsedEmail.text || "", 67 | html: parsedEmail.html || "", 68 | }; 69 | } 70 | catch (e) { 71 | console.error("Failed use PostalMime to parse email", e); 72 | } 73 | return undefined; 74 | } 75 | ``` 76 | 77 | ### 部署 78 | 79 | ```bash 80 | cd worker 81 | pnpm run deploy 82 | ``` 83 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/telegram.md: -------------------------------------------------------------------------------- 1 | # Configure Telegram Bot 2 | 3 | Try it here: [@cf_temp_mail_bot](https://t.me/cf_temp_mail_bot) 4 | 5 | ::: warning Note 6 | The default `worker.dev` domain certificate for worker is not supported by Telegram. Please use a custom domain when configuring Telegram Bot. 7 | ::: 8 | 9 | > [!NOTE] 10 | > If you want to use Telegram Bot, please bind `KV` first 11 | > 12 | > If you don't need Telegram Bot, you can skip this step 13 | > 14 | > If you want Telegram to have stronger email parsing capabilities, refer to [Configure worker to use wasm for email parsing](/en/guide/feature/mail_parser_wasm_worker) 15 | 16 | ## Telegram Bot Configuration 17 | 18 | Please first create a Telegram Bot, obtain the `token`, then execute the following command to add the `token` to secrets 19 | 20 | > [!NOTE] 21 | > If you find it troublesome, you can also put it in plain text under `[vars]` in `wrangler.toml`, but this is not recommended 22 | 23 | If you deployed via UI, you can add it under `Variables and Secrets` in the Cloudflare UI interface 24 | 25 | ```bash 26 | # Switch to worker directory 27 | cd worker 28 | pnpm wrangler secret put TELEGRAM_BOT_TOKEN 29 | ``` 30 | 31 | ## Bot 32 | 33 | - Can set whitelist users 34 | - Click `Initialize` to complete the configuration. 35 | - Click `View Status` to check the current configuration status. 36 | 37 | ![telegram](/feature/telegram.png) 38 | 39 | ## Mini App 40 | 41 | Can be deployed via command line or UI interface 42 | 43 | ### UI Deployment 44 | 45 | For other steps, refer to `Frontend and Backend Separation Deployment` in [UI Deployment](/en/guide/cli/pages) 46 | 47 | > [!NOTE] 48 | > Download the zip from here, [telegram-frontend.zip](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/telegram-frontend.zip) 49 | > 50 | > Modify the index-xxx.js file in the zip, where xx is a random string 51 | > 52 | > Search for `https://temp-email-api.xxx.xxx`, replace it with your worker domain, then deploy the new zip file 53 | 54 | ### Command Line Deployment 55 | 56 | ```bash 57 | cd frontend 58 | pnpm install 59 | cp .env.example .env.prod 60 | # --project-name can create a separate pages for mini app, you can also share one pages, but may encounter js loading issues 61 | pnpm run deploy:telegram --project-name= 62 | ``` 63 | 64 | - After deployment, please fill in the web URL in the `Settings` -> `Telegram Mini App` page `Telegram Mini App URL` in the admin backend. 65 | - Please execute `/setmenubutton` in `@BotFather`, then enter your web address to set the `Open App` button in the lower left corner. 66 | - Please execute `/newapp` in `@BotFather` to create a new app and register the mini app. 67 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/feature/new-address-api.md: -------------------------------------------------------------------------------- 1 | # 新建邮箱地址 API 2 | 3 | ## 通过 admin API 新建邮箱地址 4 | 5 | 这是一个 `python` 的例子,使用 `requests` 库发送邮件。 6 | 7 | ```python 8 | res = requests.post( 9 | # 替换 xxxx.xxxx 为你的 worker 域名 10 | "https://xxxx.xxxx/admin/new_address", 11 | json={ 12 | # 是否启用前缀 (True/False) 13 | "enablePrefix": True, 14 | "name": "<邮箱名称>", 15 | "domain": "<邮箱域名>", 16 | }, 17 | headers={ 18 | 'x-admin-auth': "<你的网站admin密码>", 19 | "Content-Type": "application/json" 20 | } 21 | ) 22 | 23 | # 返回值 {"jwt": ""} 24 | print(res.json()) 25 | ``` 26 | 27 | ## 批量创建随机用户名邮箱地址 API 示例 28 | 29 | ### 通过 admin API 批量新建邮箱地址 30 | 31 | 这是一个 `python` 的例子,使用 `requests` 库发送邮件。 32 | 33 | ```python 34 | import requests 35 | import random 36 | import string 37 | from concurrent.futures import ThreadPoolExecutor, as_completed 38 | 39 | 40 | def generate_random_name(): 41 | # 生成5位英文字符 42 | letters1 = ''.join(random.choices(string.ascii_lowercase, k=5)) 43 | # 生成1-3个数字 44 | numbers = ''.join(random.choices(string.digits, k=random.randint(1, 3))) 45 | # 生成1-3个英文字符 46 | letters2 = ''.join(random.choices(string.ascii_lowercase, k=random.randint(1, 3))) 47 | # 组合成最终名称 48 | return letters1 + numbers + letters2 49 | 50 | 51 | def fetch_email_data(name): 52 | try: 53 | res = requests.post( 54 | "https:///admin/new_address", 55 | json={ 56 | "enablePrefix": True, 57 | "name": name, 58 | "domain": "<邮箱域名>", 59 | }, 60 | headers={ 61 | 'x-admin-auth': "<你的网站admin密码>", 62 | "Content-Type": "application/json" 63 | } 64 | ) 65 | 66 | if res.status_code == 200: 67 | response_data = res.json() 68 | email = response_data.get("address", "无地址") 69 | jwt = response_data.get("jwt", "无jwt") 70 | return f"{email}----{jwt}\n" 71 | else: 72 | print(f"请求失败,状态码: {res.status_code}") 73 | return None 74 | except requests.RequestException as e: 75 | print(f"请求出现错误: {e}") 76 | return None 77 | 78 | 79 | def generate_and_save_emails(num_emails): 80 | with ThreadPoolExecutor(max_workers=30) as executor, open('email.txt', 'a') as file: 81 | futures = [executor.submit(fetch_email_data, generate_random_name()) for _ in range(num_emails)] 82 | 83 | for future in as_completed(futures): 84 | result = future.result() 85 | if result: 86 | file.write(result) 87 | 88 | 89 | # 生成10个邮箱并追加到现有文件 90 | generate_and_save_emails(10) 91 | 92 | ``` 93 | -------------------------------------------------------------------------------- /frontend/src/components/Turnstile.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 81 | 82 | 91 | -------------------------------------------------------------------------------- /worker/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | .wrangler 133 | wrangler.toml 134 | .dev.vars 135 | -------------------------------------------------------------------------------- /worker/src/telegram_api/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono' 2 | import { ServerResponse } from 'node:http' 3 | import { Writable } from 'node:stream' 4 | 5 | import { newTelegramBot, initTelegramBotCommands, sendMailToTelegram } from './telegram' 6 | import settings from './settings' 7 | import miniapp from './miniapp' 8 | 9 | export const api = new Hono(); 10 | export { sendMailToTelegram } 11 | 12 | api.use("/telegram/*", async (c, next) => { 13 | if (!c.env.TELEGRAM_BOT_TOKEN) { 14 | return c.text("TELEGRAM_BOT_TOKEN is required", 400); 15 | } 16 | if (!c.env.KV) { 17 | return c.text("KV is required", 400); 18 | } 19 | return await next(); 20 | }); 21 | 22 | api.use("/admin/telegram/*", async (c, next) => { 23 | if (!c.env.TELEGRAM_BOT_TOKEN) { 24 | return c.text("TELEGRAM_BOT_TOKEN is required", 400); 25 | } 26 | if (!c.env.KV) { 27 | return c.text("KV is required", 400); 28 | } 29 | return await next(); 30 | }); 31 | 32 | api.post("/telegram/webhook", async (c) => { 33 | const token = c.env.TELEGRAM_BOT_TOKEN; 34 | const bot = newTelegramBot(c, token); 35 | let body = null; 36 | const res = new Writable(); 37 | Object.assign(res, { 38 | headersSent: false, 39 | setHeader: (name: string, value: string) => c.header(name, value), 40 | end: (data: any) => body = data, 41 | }); 42 | const reqJson = await c.req.json(); 43 | await bot.handleUpdate(reqJson, res as ServerResponse); 44 | return c.body(body); 45 | }); 46 | 47 | api.post("/admin/telegram/init", async (c) => { 48 | const domain = new URL(c.req.url).host; 49 | const token = c.env.TELEGRAM_BOT_TOKEN; 50 | const webhookUrl = `https://${domain}/telegram/webhook`; 51 | console.log(`setting webhook to ${webhookUrl}`); 52 | const bot = newTelegramBot(c, token); 53 | await bot.telegram.setWebhook(webhookUrl) 54 | await initTelegramBotCommands(bot); 55 | return c.json({ 56 | message: "webhook set successfully", 57 | }); 58 | }); 59 | 60 | api.get("/admin/telegram/status", async (c) => { 61 | const token = c.env.TELEGRAM_BOT_TOKEN; 62 | const bot = newTelegramBot(c, token); 63 | const info = await bot.telegram.getWebhookInfo() 64 | const commands = await bot.telegram.getMyCommands() 65 | return c.json({ info, commands }); 66 | }); 67 | 68 | api.get("/admin/telegram/settings", settings.getTelegramSettings); 69 | api.post("/admin/telegram/settings", settings.saveTelegramSettings); 70 | api.post("/telegram/get_bind_address", miniapp.getTelegramBindAddress); 71 | api.post("/telegram/new_address", miniapp.newTelegramAddress); 72 | api.post("/telegram/bind_address", miniapp.bindAddress); 73 | api.post("/telegram/unbind_address", miniapp.unbindAddress); 74 | api.post("/telegram/get_mail", miniapp.getMail); 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/ 3 | test/ 4 | .vscode/ 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | .pnpm-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional stylelint cache 62 | .stylelintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variable files 80 | .env 81 | .env.development.local 82 | .env.test.local 83 | .env.production.local 84 | .env.local 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | .parcel-cache 89 | 90 | # Next.js build output 91 | .next 92 | out 93 | 94 | # Nuxt.js build / generate output 95 | .nuxt 96 | dist 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and not Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # vuepress v2.x temp and cache directory 108 | .temp 109 | .cache 110 | 111 | # Docusaurus cache and generated files 112 | .docusaurus 113 | 114 | # Serverless directories 115 | .serverless/ 116 | 117 | # FuseBox cache 118 | .fusebox/ 119 | 120 | # DynamoDB Local files 121 | .dynamodb/ 122 | 123 | # TernJS port file 124 | .tern-port 125 | 126 | # Stores VSCode versions used for testing VSCode extensions 127 | .vscode-test 128 | 129 | # yarn v2 130 | .yarn/cache 131 | .yarn/unplugged 132 | .yarn/build-state.yml 133 | .yarn/install-state.gz 134 | .pnp.* 135 | 136 | .wrangler 137 | wrangler.toml 138 | .dev.vars 139 | pnpm-lock.yaml 140 | -------------------------------------------------------------------------------- /vitepress-docs/docs/zh/guide/common-issues.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | > [!NOTE] 注意 4 | > 如果你的问题没有在这里找到解决方案,请到 `Github Issues` 中搜索或者提问, 或者到 Telegram 群组中提问。 5 | 6 | ## 通用 7 | 8 | | 问题 | 解决方案 | 9 | | -------------------------------------------------- | ------------------------------------------------------------------------------- | 10 | | 使用 Cloudflare Workers 给已认证的转发邮箱发送邮件 | 使用 cf 的 API 进行发送,只支持绑定到 CF 上的收件地址,即 CF EMAIL 转发目的地址 | 11 | | 绑定多个域名 | 每个域名都需要设置 email 转发到 worker | 12 | 13 | ## worker 相关 14 | 15 | | 问题 | 解决方案 | 16 | | ------------------------------------------------------------------ | --------------------------------------------------------------------------- | 17 | | `Uncaught Error: No such module "path". imported from "worker.js"` | [参考](/zh/guide/ui/worker) | 18 | | `No such module "node:stream". imported from "worker.js"` | [参考](/zh/guide/ui/worker) | 19 | | `二级域名无法发送邮件` | [参考](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/515) | 20 | | `Failed to send verify code: No balance` | admin 后台设置无限制邮件或者发件权限页面增加额度 | 21 | | `Github OAuth无法获取到邮箱 400 Failed to get user email` | 需要 github 用户设置公开邮箱 | 22 | | `Cannot read properties of undefined (reading 'map')` | worker 变量没有设置成功 | 23 | 24 | ## pages 相关 25 | 26 | | 问题 | 解决方案 | 27 | | --------------- | ---------------------------------------- | 28 | | `network error` | 使用无痕模式或者清空浏览器缓存,DNS 缓存 | 29 | 30 | ## telegram bot 31 | 32 | | 问题 | 解决方案 | 33 | | -------------------------------------------------------------- | -------------------------------------------------- | 34 | | `Telgram Bot获取邮件失败:400:Bad Request:BUTTON_URL_INVALID` | tg mini app 的 URL 填写错误,需要填写 pages 的 URL | 35 | | `Telegram bot bind error: bind adress count reach the limit` | 需要设置 worker 变量 `TG_MAX_ADDRESS` | 36 | 37 | ## Github Actions 38 | 39 | | 问题 | 解决方案 | 40 | | ------------------------------------------ | --------------------------------------------------------------------------------- | 41 | | Github Action部署后,cf里始终是preview分支 | 到 cf pages 页面的设置中确认 前端的分支 和 Github Action 的 前端部署分支 是否相同 | 42 | -------------------------------------------------------------------------------- /worker/src/mails_api/webhook_settings.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { CONSTANTS } from "../constants"; 3 | import { AdminWebhookSettings, WebhookSettings } from "../models"; 4 | import { commonParseMail, sendWebhook } from "../common"; 5 | 6 | 7 | async function getWebhookSettings(c: Context): Promise { 8 | const { address } = c.get("jwtPayload") 9 | const adminSettings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json"); 10 | if (adminSettings?.enableAllowList && !adminSettings?.allowList.includes(address)) { 11 | return c.text("Webhook settings is not allowed for this user", 403); 12 | } 13 | const settings = await c.env.KV.get( 14 | `${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`, "json" 15 | ) || new WebhookSettings(); 16 | return c.json(settings); 17 | } 18 | 19 | 20 | async function saveWebhookSettings(c: Context): Promise { 21 | const { address } = c.get("jwtPayload") 22 | const adminSettings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json"); 23 | if (adminSettings?.enableAllowList && !adminSettings?.allowList.includes(address)) { 24 | return c.text("Webhook settings is not allowed for this user", 403); 25 | } 26 | const settings = await c.req.json(); 27 | await c.env.KV.put( 28 | `${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`, 29 | JSON.stringify(settings)); 30 | return c.json({ success: true }) 31 | } 32 | 33 | async function testWebhookSettings(c: Context): Promise { 34 | const settings = await c.req.json(); 35 | const { address } = c.get("jwtPayload"); 36 | // random raw email 37 | const { id: mailId, raw } = await c.env.DB.prepare( 38 | `SELECT id, raw FROM raw_mails WHERE address = ? ORDER BY RANDOM() LIMIT 1` 39 | ).bind(address).first<{ id: string, raw: string }>() || {}; 40 | const parsedEmailContext: ParsedEmailContext = { rawEmail: raw || "" }; 41 | const parsedEmail = await commonParseMail(parsedEmailContext); 42 | const res = await sendWebhook(settings, { 43 | id: mailId || "0", 44 | url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "", 45 | from: parsedEmail?.sender || "test@test.com", 46 | to: address, 47 | subject: parsedEmail?.subject || "test subject", 48 | raw: raw || "test raw email", 49 | parsedText: parsedEmail?.text || "test parsed text", 50 | parsedHtml: parsedEmail?.html || "test parsed html" 51 | }); 52 | if (!res.success) { 53 | return c.text(res.message || "send webhook error", 400); 54 | } 55 | return c.json({ success: true }); 56 | } 57 | 58 | export default { 59 | getWebhookSettings, 60 | saveWebhookSettings, 61 | testWebhookSettings, 62 | } 63 | -------------------------------------------------------------------------------- /frontend/src/views/user/UserMailBox.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 90 | -------------------------------------------------------------------------------- /vitepress-docs/docs/en/guide/feature/new-address-api.md: -------------------------------------------------------------------------------- 1 | # Create New Email Address API 2 | 3 | ## Create Email Address via Admin API 4 | 5 | This is a `python` example using the `requests` library to send emails. 6 | 7 | ```python 8 | res = requests.post( 9 | # Replace xxxx.xxxx with your worker domain 10 | "https://xxxx.xxxx/admin/new_address", 11 | json={ 12 | # Enable prefix (True/False) 13 | "enablePrefix": True, 14 | "name": "", 15 | "domain": "", 16 | }, 17 | headers={ 18 | 'x-admin-auth': "", 19 | "Content-Type": "application/json" 20 | } 21 | ) 22 | 23 | # Returns {"jwt": ""} 24 | print(res.json()) 25 | ``` 26 | 27 | ## Batch Create Random Username Email Addresses API Example 28 | 29 | ### Batch Create Email Addresses via Admin API 30 | 31 | This is a `python` example using the `requests` library to send emails. 32 | 33 | ```python 34 | import requests 35 | import random 36 | import string 37 | from concurrent.futures import ThreadPoolExecutor, as_completed 38 | 39 | 40 | def generate_random_name(): 41 | # Generate 5 lowercase letters 42 | letters1 = ''.join(random.choices(string.ascii_lowercase, k=5)) 43 | # Generate 1-3 digits 44 | numbers = ''.join(random.choices(string.digits, k=random.randint(1, 3))) 45 | # Generate 1-3 lowercase letters 46 | letters2 = ''.join(random.choices(string.ascii_lowercase, k=random.randint(1, 3))) 47 | # Combine into final name 48 | return letters1 + numbers + letters2 49 | 50 | 51 | def fetch_email_data(name): 52 | try: 53 | res = requests.post( 54 | "https:///admin/new_address", 55 | json={ 56 | "enablePrefix": True, 57 | "name": name, 58 | "domain": "", 59 | }, 60 | headers={ 61 | 'x-admin-auth': "", 62 | "Content-Type": "application/json" 63 | } 64 | ) 65 | 66 | if res.status_code == 200: 67 | response_data = res.json() 68 | email = response_data.get("address", "no address") 69 | jwt = response_data.get("jwt", "no jwt") 70 | return f"{email}----{jwt}\n" 71 | else: 72 | print(f"Request failed, status code: {res.status_code}") 73 | return None 74 | except requests.RequestException as e: 75 | print(f"Request error: {e}") 76 | return None 77 | 78 | 79 | def generate_and_save_emails(num_emails): 80 | with ThreadPoolExecutor(max_workers=30) as executor, open('email.txt', 'a') as file: 81 | futures = [executor.submit(fetch_email_data, generate_random_name()) for _ in range(num_emails)] 82 | 83 | for future in as_completed(futures): 84 | result = future.result() 85 | if result: 86 | file.write(result) 87 | 88 | 89 | # Generate 10 emails and append to existing file 90 | generate_and_save_emails(10) 91 | 92 | ``` 93 | --------------------------------------------------------------------------------