diff --git a/.gitignore b/.gitignore index b90dbbc..ebeea4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /backend/node_modules /frontend/node_modules /backend/package-lock.json -/frontend/package-lock.json \ No newline at end of file +/frontend/package-lock.json +/frontend/dist \ No newline at end of file diff --git a/README.md b/README.md index 1599250..2f30038 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,123 @@ -# Email Unlimit Project (临时邮件项目) +# 轻量级临时邮件项目 (Email Unlimit) -## 最终架构 (单一 Docker Compose + 宿主机 Nginx) +本项目是一个轻量级的、可自托管的临时邮件解决方案。它允许您使用自己的域名接收邮件,并通过一个简洁的网页界面来查看这些邮件。 -本项目采用生产环境推荐的架构:所有服务(包括您的应用和完整的 Mailu 邮件套件)都由一个 `docker-compose.full.yml` 文件统一管理,而入口的反向代理则由 **宿主机上的 Nginx** 负责。 +与 `Mailu` 等复杂的邮件套件不同,本项目采用了一个极简的 Node.js 服务来直接接收和处理邮件,部署和维护都非常简单。 -- **宿主机 Nginx:** 作为项目的唯一公共入口 (`main.shenjianl.cn`)。它负责: - 1. 处理所有外部流量和 SSL 加密(推荐)。 - 2. 将对域名根路径 (`/`) 的访问反向代理到 **前端 Docker 容器** (`localhost:5181`)。 - 3. 将对 `/api` 路径的访问反向代理到 **后端 Docker 容器** (`localhost:5182`)。 - 4. 将对 `/mailu` 路径的访问反向代理到 **Mailu Admin UI** (`localhost:80`)。 +## 技术架构 -- **Docker 容器 (由 `docker-compose.full.yml` 管理):** - - **您的应用:** `frontend`, `backend`, `mysql`。 - - **Mailu 套件:** `front` (Mailu Nginx), `admin`, `smtp`, `imap`, `redis` 等全套服务。 +* **前端 (Frontend)**: 使用 Vue.js 构建的单页面应用,负责展示收到的邮件列表。 +* **后端 (Backend)**: + * 使用 Node.js 和 Express 搭建的 API 服务器。 + * 内置一个轻量级的 SMTP 服务器 (`smtp-server`),用于直接接收邮件,无需外部邮件服务。 + * 负责将收到的邮件解析并存入数据库。 +* **数据库 (Database)**: 需要一个外部的 MySQL 数据库来存储邮件信息。 +* **部署 (Deployment)**: 后端服务通过 Docker Compose 进行容器化部署,前端静态文件由宿主机的 Nginx 提供服务。 ---- +## 部署要求 -## 如何运行 +在开始之前,��确保您已准备好以下环境: -### 步骤 1: 准备工作 +1. 一台拥有公网 IP 的 Linux 服务器。 +2. 一个您自己的域名。 +3. 服务器上已安装 `Docker` 和 `Docker Compose`。 +4. 服务器上已安装 `Nginx`。 +5. 一个可用的外部 MySQL 数据库,并已创建好数据库。 +6. 本地开发环境已安装 `Node.js` 和 `npm` (用于构建前端)。 -1. **安装 Docker**: 确保您的系统中已安装 Docker 和 Docker Compose。 -2. **配置 Mailu**: 打开 `mailu.env` 文件,**务必修改** `SECRET_KEY` 和 `ADMIN_PASSWORD` 为安全的值。域名和数据库配置已预填。 -3. **配置 DNS**: 根据 `info.md` 中的 DNS 记录示例,在您的域名提供商处完成 SPF、DKIM 和 DMARC 的配置。 +## 部署步骤 -### 步骤 2: 配置宿主机 Nginx +### 步骤 1: 配置域名 DNS -将下面的配置块添加到您宿主机的 Nginx 中。这份配置统一处理了对您的应用和 Mailu 管理后台的访问。 +要让邮件能正确发送到您的服务器,您必须配置域名的 `MX` 记录。 -```nginx -# /etc/nginx/conf.d/main.shenjianl.cn.conf +1. 登录您的域名注册商(如 GoDaddy, Cloudflare 等)。 +2. 找到 DNS 解析设置。 +3. 添加一条 `MX` 记录: + * **类型 (Type)**: `MX` + * **名称 (Name/Host)**: `@` (代表您的根域名) + * **值 (Value/Points to)**: `您的服务器公网 IP 地址` + * **优先级 (Priority)**: `10` -server { - listen 80; - server_name main.shenjianl.cn; +> **注意**: DNS 记录生效可能需要几分钟到几小时。 - # --- 推荐配置 SSL --- - # listen 443 ssl http2; - # server_name main.shenjianl.cn; - # ssl_certificate /path/to/your/fullchain.pem; - # ssl_certificate_key /path/to/your/privkey.pem; - # -------------------- +### 步骤 2: 部署后端服务 - # 反向代理到前端 Vue.js 容器 - location / { - proxy_pass http://localhost:5181; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; +1. 将本项目克隆或上传到您的服务器。 +2. 进入项目根目录,编辑 `docker-compose.yml` 文件。 +3. **填写您的外部数据库连接信息**: + ```yaml + services: + backend: + # ... + environment: + - DB_HOST=your_external_db_host # 替换为您的外部数据库主机名或IP + - DB_USER=your_external_db_user # 替换为您的数据库用户名 + - DB_PASSWORD=your_external_db_password # 替换为您的数据库密码 + - DB_NAME=your_external_db_name # 替换为您的数据库名称 + ``` +4. 在 `backend` 目录下有一个 `init.sql` 文件,请手动将其中的 SQL 命令在您的外部数据库中执行,以创建所需的表。 +5. 在项目根目录,使用 Docker Compose 启动后端服务: + ```bash + docker-compose up -d --build + ``` + 此命令会构建并以后台模式启动后端容器。服务将监听服务器的 `5182` (API) 和 `25` (SMTP) 端口。 + +### 步骤 3: 构建和部署前端 + +1. **在您的本地开发机上**,进入 `frontend` 目录。 +2. 安装依赖并构建静态文件: + ```bash + npm install + npm run build + ``` + 这将在 `frontend/dist` 目录下生成所有用于部署的静态文件。 +3. 将 `frontend/dist` 目录下的 **所有文件** 上传到您服务器的指定位置,例如 `/var/www/email-unlimit`。 + +### 步骤 4: 配置宿主机 Nginx + +1. 在服务器上,为您的应用创建一个 Nginx 配置文件,例如 `/etc/nginx/sites-available/email.conf`。 +2. 将以下配置写入该文件。请务必将 `your_domain.com` 和 `root` 路径修改为您自己的配置。 + + ```nginx + server { + listen 443 ssl; + server_name mail.shenjianl.cn; # 替换为您的域名 + ssl_certificate /usr/local/nginx/conf/ssl_certificate/mail/mail.shenjianl.cn_bundle.pem; + ssl_certificate_key /usr/local/nginx/conf/ssl_certificate/mail/mail.shenjianl.cn.key; + # 前端静态文件路径 + root /data/email-unlimit/frontend/dist; + index index.html; + + # 处理 Vue Router 的 history 模式 + location / { + try_files $uri $uri/ /index.html; + } + + # 将 /api 请求反向代理到后端 Docker 容器 + location /api { + proxy_pass http://localhost:5182; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } } + ``` +3. 启用该配置并重启 Nginx: + ```bash + # 创建软链接 + sudo ln -s /etc/nginx/sites-available/email.conf /etc/nginx/sites-enabled/ + + # 测试配置语法 + sudo nginx -t + + # 重启 Nginx + sudo systemctl restart nginx + ``` - # 反向代理到后端 API 容器 - location /api { - proxy_pass http://localhost:5182; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } +## 如何使用 - # 反向代理到 Mailu Admin UI 和 Webmail - # 注意:Mailu 自己的 Nginx (front) 在 80 端口上运行 - location /mailu { - proxy_pass http://localhost:80; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } -} -``` -**配置完成后,请重载或重启您的 Nginx 服务。** - -### 步骤 3: 启动 Docker 服务 - -使用我们最终生成的 `docker-compose.full.yml` 文件来启动所有服务。 - -```bash -# 在项目根目录执行 -docker-compose -f docker-compose.full.yml up -d --build -``` - ---- - -## 访问���址 - -- **主应用入口**: `http://main.shenjianl.cn` -- **Mailu 管理后台**: `http://main.shenjianl.cn/mailu` -- **Mailu Webmail**: `http://main.shenjianl.cn/mailu/webmail` - -docker-compose up -d --build \ No newline at end of file +1. **访问您的网站**: 在浏览器中打开 `http://your_domain.com`。 +2. **发送测试邮件**: 使用任何邮箱客户端,向 `anything@your_domain.com` (例如 `test@your_domain.com`) 发送一封邮件。 +3. **查看邮件**: 在网站上输入您刚刚使用的收件人地址 (`anything@your_domain.com`),点击查询,即可看到收到的邮件。 diff --git a/backend/Dockerfile b/backend/Dockerfile index abf12ef..de47d81 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,6 +7,14 @@ RUN npm install COPY . . +# Expose API port and SMTP port EXPOSE 5182 +EXPOSE 25 + +# Environment variables for database connection will be passed at runtime +# ENV DB_HOST=... +# ENV DB_USER=... +# ENV DB_PASSWORD=... +# ENV DB_NAME=... CMD [ "node", "app.js" ] diff --git a/backend/app.js b/backend/app.js index 462a527..5d67785 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,9 +1,12 @@ const express = require('express'); const cors = require('cors'); const db = require('./db'); +const { SMTPServer } = require('smtp-server'); +const { saveEmail } = require('./saveEmail'); const app = express(); -const port = 5182; +const apiPort = 5182; +const smtpPort = 25; app.use(cors()); app.use(express.json()); @@ -27,6 +30,33 @@ app.get('/api/messages', async (req, res) => { } }); -app.listen(port, () => { - console.log(`Backend server listening at http://localhost:${port}`); +// Start API server +app.listen(apiPort, () => { + console.log(`Backend API server listening at http://localhost:${apiPort}`); +}); + +// Configure and start SMTP server +const smtpServer = new SMTPServer({ + authOptional: true, + disabledCommands: ['AUTH'], + onData(stream, session, callback) { + console.log('Receiving email...'); + saveEmail(stream) + .then(() => { + console.log('Email processed and saved successfully.'); + callback(); // Accept the message + }) + .catch(err => { + console.error('Error processing email:', err); + callback(new Error('Failed to process email.')); + }); + }, +}); + +smtpServer.on('error', err => { + console.error('SMTP Server Error:', err.message); +}); + +smtpServer.listen(smtpPort, () => { + console.log(`SMTP server listening on port ${smtpPort}`); }); diff --git a/backend/package.json b/backend/package.json index 0ceb14a..3f23002 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,6 +11,6 @@ "mysql2": "^3.14.2", "mailparser": "^3.7.4", "cors": "^2.8.5", - "get-stream": "^9.0.1" + "smtp-server": "^3.13.4" } } diff --git a/backend/saveEmail.js b/backend/saveEmail.js index cfd31a6..15d376c 100644 --- a/backend/saveEmail.js +++ b/backend/saveEmail.js @@ -1,16 +1,11 @@ -#!/usr/bin/env node - const { simpleParser } = require('mailparser'); const db = require('./db'); -// Using require for get-stream v5 as it's CommonJS -const getStream = require('get-stream'); - -async function saveEmail(rawEmail) { +async function saveEmail(stream) { try { - const parsed = await simpleParser(rawEmail); + const parsed = await simpleParser(stream); + const rawEmail = stream.toString(); // Or handle stream to buffer conversion appropriately - // Ensure 'to' and 'from' objects exist before accessing 'text' const recipient = parsed.to ? parsed.to.text : 'undisclosed-recipients'; const sender = parsed.from ? parsed.from.text : 'unknown-sender'; const subject = parsed.subject; @@ -21,7 +16,7 @@ async function saveEmail(rawEmail) { [recipient, sender, subject, body, rawEmail] ); - console.log(`Email saved with ID: ${result.insertId}`); + console.log(`Email from <${sender}> to <${recipient}> saved with ID: ${result.insertId}`); if (parsed.attachments && parsed.attachments.length > 0) { for (const attachment of parsed.attachments) { @@ -34,21 +29,11 @@ async function saveEmail(rawEmail) { } } catch (error) { console.error('Failed to save email:', error); - // Exit with an error code to signal failure to the calling process (e.g., Mailu) - process.exit(1); + // We should not exit the process here, but maybe throw the error + // so the caller (SMTPServer) can handle it. + throw error; } } -(async () => { - try { - const rawEmail = await getStream(process.stdin); - if (rawEmail && rawEmail.length > 0) { - await saveEmail(rawEmail); - } else { - console.log('Received empty input, no email to save.'); - } - } catch (error) { - console.error('Error reading from stdin:', error); - process.exit(1); - } -})(); +module.exports = { saveEmail }; + diff --git a/docker-compose.full.yml b/docker-compose.full.yml deleted file mode 100644 index a5a10c0..0000000 --- a/docker-compose.full.yml +++ /dev/null @@ -1,118 +0,0 @@ -# docker-compose.full.yml -# 最终合并版本:包含您的应用 (frontend, backend) 和完整的 Mailu 服务套件。 - -version: '3.3' - -services: - - # ----------------------------------------- - # 您的应用服务 - # ----------------------------------------- - - backend: - build: ./backend - container_name: mail_backend - restart: always - environment: - DB_HOST: "43.143.145.172" - DB_USER: "root" - DB_PASSWORD: "kyff145972" - DB_NAME: "maildb" - ports: - - "5182:5182" - - frontend: - build: ./frontend - container_name: mail_frontend - restart: always - ports: - - "5181:8080" - depends_on: - - backend - - # ----------------------------------------- - # Mailu 官方服务套件 - # ----------------------------------------- - - redis: - image: redis:alpine - restart: always - volumes: - - "mailu_redis:/data" - - front: - image: ghcr.io/mailu/nginx:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_certs:/certs" - - "mailu_overrides_nginx:/overrides" - - resolver: - image: ghcr.io/mailu/unbound:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - dns: - - 8.8.8.8 - - 1.1.1.1 - # --- 用于调试的临时修改 --- - entrypoint: /bin/sh - command: -c "sleep 3600" - - admin: - image: ghcr.io/mailu/admin:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_data:/data" - - "mailu_dkim:/dkim" - depends_on: - - redis - - imap: - image: ghcr.io/mailu/dovecot:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_mail:/mail" - - "mailu_overrides_dovecot:/overrides" - depends_on: - - front - - smtp: - image: ghcr.io/mailu/postfix:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_overrides_postfix:/overrides" - depends_on: - - front - - resolver - - antispam: - image: ghcr.io/mailu/rspamd:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_filter:/var/lib/rspamd" - - "mailu_overrides_rspamd:/etc/rspamd/override.d" - depends_on: - - front - -volumes: - mailu_data: - mailu_certs: - mailu_dkim: - mailu_filter: - mailu_mail: - mailu_redis: - mailu_overrides_nginx: - mailu_overrides_dovecot: - mailu_overrides_postfix: - mailu_overrides_rspamd: diff --git a/docker-compose.yml b/docker-compose.yml index 7b5b7c9..7e4b00c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,115 +1,21 @@ -# docker-compose.full.yml -# 最终合并版本:包含您的应用 (frontend, backend) 和完整的 Mailu 服务套件。 - version: '3.3' services: - - # ----------------------------------------- - # 您的应用服务 - # ----------------------------------------- - backend: build: ./backend - container_name: mail_backend + container_name: email-backend-container restart: always + ports: + - "5182:5182" # API port + - "25:25" # SMTP port environment: - DB_HOST: "43.143.145.172" - DB_USER: "root" - DB_PASSWORD: "kyff145972" - DB_NAME: "maildb" - ports: - - "5182:5182" + - DB_HOST=43.143.145.172 # 替换为您的外部数据库主机名或IP + - DB_USER=root # 替换为您的数据库用户名 + - DB_PASSWORD=kyff145972 # 替换为您的数据库密码 + - DB_NAME=maildb # 替换为您的数据库名称 + networks: + - email-network - frontend: - build: ./frontend - container_name: mail_frontend - restart: always - ports: - - "5181:8080" - depends_on: - - backend - - # ----------------------------------------- - # Mailu 官方服务套件 - # ----------------------------------------- - - redis: - image: redis:alpine - restart: always - volumes: - - "mailu_redis:/data" - - front: - image: ghcr.io/mailu/nginx:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_certs:/certs" - - "mailu_overrides_nginx:/overrides" - - resolver: - image: ghcr.io/mailu/unbound:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - dns: - - 8.8.8.8 - - 1.1.1.1 - - admin: - image: ghcr.io/mailu/admin:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_data:/data" - - "mailu_dkim:/dkim" - depends_on: - - redis - - imap: - image: ghcr.io/mailu/dovecot:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_mail:/mail" - - "mailu_overrides_dovecot:/overrides" - depends_on: - - front - - smtp: - image: ghcr.io/mailu/postfix:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_overrides_postfix:/overrides" - depends_on: - - front - - resolver - - antispam: - image: ghcr.io/mailu/rspamd:2.0 - restart: always - env_file: mailu.env - hostname: mail.shenjianl.cn - volumes: - - "mailu_filter:/var/lib/rspamd" - - "mailu_overrides_rspamd:/etc/rspamd/override.d" - depends_on: - - front - -volumes: - mailu_data: - mailu_certs: - mailu_dkim: - mailu_filter: - mailu_mail: - mailu_redis: - mailu_overrides_nginx: - mailu_overrides_dovecot: - mailu_overrides_postfix: - mailu_overrides_rspamd: +networks: + email-network: + driver: bridge \ No newline at end of file diff --git a/mailu.env b/mailu.env deleted file mode 100644 index 0a36ebf..0000000 --- a/mailu.env +++ /dev/null @@ -1,26 +0,0 @@ -SECRET_KEY=d4f7a1b3c2e987f05a6d3b4c8e1f2097 -DOMAIN=shenjianl.cn -HOSTNAMES=mail.shenjianl.cn -POSTMASTER=admin - -DB_FLAVOR=mysql -DB_HOST=43.143.145.172 -DB_USER=root -DB_PASSWORD=kyff145972 -DB_NAME=maildb - -WEB_ADMIN=/mailu -WEB_WEBMAIL=/mailu/webmail - -ENABLE_UNPRIVILEGED_USER=false -PASSWORD_SCHEME=BLF-CRYPT -ENABLE_ADMIN=true -ENABLE_WEBMAIL=true -ENABLE_WEBDAV=false -ENABLE_ANTIVIRUS=false -ENABLE_ANTISPAM=true -DEFAULT_QUOTA=2G -ADMIN_PASSWORD=admin - -TLS_FLAVOR=notls -MESSAGE_SIZE_LIMIT=20000000