使用AI开发一个临时邮箱系统|10分钟邮箱系统

这是一个基于 Node.js 的临时邮箱系统,支持多语言、HTTPS、固定域名选择、任意域名 MX 解析接收邮件等功能。以下是完整的搭建和运行指南。
功能概览
  • 多语言支持:简体中文、繁体中文、英文、意大利语、西班牙语。
  • 邮箱有效期:默认 10 分钟,支持手动延长。
  • 复制功能:一键复制邮箱地址。
  • HTTPS 支持:通过 Nginx 和 Let’s Encrypt 实现。
  • 域名支持
    • 主域名(如 email.yourdomain.com)用于访问程序。
    • 固定域名(如 yourdomain1.com)供用户选择。
    • 任意域名通过 MX 记录接收邮件。
  • 进程常驻:使用 PM2 保持后台运行。
前置要求
  • 操作系统:Linux(推荐 Ubuntu)
  • 软件
    • Node.js(建议 v16 或以上)
    • Nginx
    • Certbot(用于 HTTPS)
    • PM2(用于进程管理)
  • 硬件:一台具有公网 IP 的服务器
  • 域名:至少一个主域名和几个固定域名

项目结构
temp-email/
├── index.js              # 主程序
├── package.json          # 依赖配置文件
├── emails/               # 邮件存储目录
├── public/
│   ├── index.html        # 前端页面
│   └── languages/        # 语言文件
│       ├── en.json
│       ├── zh-CN.json
│       ├── zh-TW.json
│       ├── it.json
│       └── es.json
├── cert/                 # HTTPS 证书目录
│   ├── server.key
│   └── server.crt

步骤 1:准备环境
1.1 安装 Node.js
sudo apt update
sudo apt install nodejs npm
node -v  # 确认版本,例如 v16.x.x
1.2 安装 Nginx
sudo apt install nginx
sudo systemctl enable nginx
sudo systemctl start nginx
1.3 安装 Certbot
sudo apt install certbot python3-certbot-nginx
1.4 安装 PM2
npm install -g pm2
1.5 获取服务器 IP
curl ifconfig.me  # 记录公网 IP,例如 123.45.67.89

步骤 2:配置项目文件
2.1 创建项目目录
mkdir temp-email
cd temp-email
2.2 创建 index.js
const { SMTPServer } = require('smtp-server');
const express = require('express');
const { simpleParser } = require('mailparser');
const path = require('path');
const fs = require('fs');
const https = require('https');

// 配置
const PORT_WEB = 3000;
const PORT_SMTP = 25;
const MAIN_DOMAIN = 'email.yourdomain.com'; // 替换为主域名
const FIXED_DOMAINS = ['yourdomain1.com', 'yourdomain2.com', 'yourdomain3.com']; // 替换为固定域名
const EMAIL_TTL = 24 * 60 * 60 * 1000; // 文件存储 24 小时
const EMAIL_LIFETIME = 10 * 60 * 1000; // 邮箱默认 10 分钟有效期
const EMAIL_DIR = path.join(__dirname, 'emails');

if (!fs.existsSync(EMAIL_DIR)) {
  fs.mkdirSync(EMAIL_DIR);
}

const emailLifetimes = new Map();

function generateEmail(prefix, domain) {
  const randomStr = prefix || Math.random().toString(36).substring(2, 10);
  const cleanPrefix = randomStr.replace(/[^a-zA-Z0-9]/g, '');
  if (!cleanPrefix) throw new Error('Invalid prefix');
  return `${cleanPrefix}@${domain}`;
}

function loadEmails(email) {
  const filePath = path.join(EMAIL_DIR, `${email.replace('@', '_')}.json`);
  if (fs.existsSync(filePath)) {
    return JSON.parse(fs.readFileSync(filePath, 'utf8'));
  }
  return [];
}

function saveEmails(email, emails) {
  const filePath = path.join(EMAIL_DIR, `${email.replace('@', '_')}.json`);
  fs.writeFileSync(filePath, JSON.stringify(emails, null, 2));
}

function cleanupEmails() {
  const now = Date.now();
  fs.readdirSync(EMAIL_DIR).forEach(file => {
    const email = file.replace('_', '@').replace('.json', '');
    const emails = loadEmails(email);
    const filtered = emails.filter(e => now - e.timestamp < EMAIL_TTL);
    if (filtered.length > 0) {
      saveEmails(email, filtered);
    } else {
      fs.unlinkSync(path.join(EMAIL_DIR, file));
    }
  });
}
setInterval(cleanupEmails, 60 * 1000);

const smtpServer = new SMTPServer({
  authOptional: true,
  onRcptTo(address, session, callback) {
    callback(null);
  },
  onData(stream, session, callback) {
    let emailData = '';
    stream.on('data', chunk => (emailData += chunk));
    stream.on('end', async () => {
      try {
        const parsed = await simpleParser(emailData);
        const to = session.envelope.rcptTo[0].address;

        const expiration = emailLifetimes.get(to);
        if (expiration && Date.now() > expiration) {
          return callback(new Error('Email address expired'));
        }

        const emailEntry = {
          from: parsed.from?.value[0]?.address || 'unknown@example.com',
          fromName: parsed.from?.value[0]?.name || '',
          subject: parsed.subject || 'No Subject',
          body: parsed.text || parsed.html || 'No Content',
          timestamp: Date.now(),
          receivedAt: new Date().toLocaleString()
        };

        const emails = loadEmails(to);
        emails.push(emailEntry);
        saveEmails(to, emails);
        callback(null);
      } catch (err) {
        console.error('Error parsing email:', err);
        callback(err);
      }
    });
  }
});

smtpServer.listen(PORT_SMTP, () => {
  console.log(`SMTP Server running on port ${PORT_SMTP}`);
});

const app = express();
app.use(express.static(path.join(__dirname, 'public')));

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

app.get('/api/main-domain', (req, res) => {
  res.json({ domain: MAIN_DOMAIN });
});

app.get('/api/domains', (req, res) => {
  res.json(FIXED_DOMAINS);
});

app.get('/api/new-email', (req, res) => {
  const domain = req.query.domain || FIXED_DOMAINS[0]; // 如果没有提供 domain,使用默认域名
  const prefix = req.query.prefix || '';
  console.log(`Received domain: ${domain}`); // 调试日志
  try {
    const email = generateEmail(prefix, domain);
    emailLifetimes.set(email, Date.now() + EMAIL_LIFETIME);
    res.json({ email, expiresAt: emailLifetimes.get(email) });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.get('/api/emails/:email', (req, res) => {
  const email = req.params.email;
  const emails = loadEmails(email);
  const expiration = emailLifetimes.get(email) || 0;
  res.json({
    emails: emails.filter(e => Date.now() - e.timestamp < EMAIL_TTL),
    expiresAt: expiration
  });
});

app.get('/api/extend/:email', (req, res) => {
  const email = req.params.email;
  if (emailLifetimes.has(email)) {
    const newExpiration = (emailLifetimes.get(email) || Date.now()) + EMAIL_LIFETIME;
    emailLifetimes.set(email, newExpiration);
    res.json({ expiresAt: newExpiration });
  } else {
    res.status(404).json({ error: 'Email not found or already expired' });
  }
});

const useHttps = fs.existsSync(path.join(__dirname, 'cert', 'server.key')) && 
                 fs.existsSync(path.join(__dirname, 'cert', 'server.crt'));

if (useHttps) {
  const options = {
    key: fs.readFileSync(path.join(__dirname, 'cert', 'server.key')),
    cert: fs.readFileSync(path.join(__dirname, 'cert', 'server.crt'))
  };
  https.createServer(options, app).listen(PORT_WEB, 'localhost', () => {
    console.log(`HTTPS Server running on https://localhost:${PORT_WEB}`);
  });
} else {
  app.listen(PORT_WEB, 'localhost', () => {
    console.log(`HTTP Server running on http://localhost:${PORT_WEB}`);
  });
}
2.3 创建 package.json
{
  "name": "temp-email",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "smtp-server": "^3.11.0",
    "mailparser": "^3.6.5",
    "https": "^1.0.0",
    "fs": "^0.0.1-security"
  }
}
2.4 创建 public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Temp Email</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f4f6f9;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }
        .container {
            max-width: 900px;
            width: 100%;
            background: white;
            border-radius: 12px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
            padding: 25px;
            text-align: center;
            position: relative;
        }
        h1 {
            font-size: 26px;
            color: #1565c0;
            margin-bottom: 20px;
        }
        .intro, .tutorial {
            font-size: 14px;
            color: #666;
            margin-top: 20px;
            text-align: left;
            padding: 10px;
            background: #f9f9f9;
            border-radius: 6px;
        }
        .intro h2, .tutorial h2 {
            font-size: 18px;
            color: #1976d2;
            margin-bottom: 10px;
        }
        .email-section {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 15px;
            padding-bottom: 20px;
            border-bottom: 1px solid #ddd;
        }
        .email-box {
            flex-grow: 1;
            max-width: 500px;
            padding: 12px;
            background: #e3f2fd;
            border-radius: 8px;
            font-size: 18px;
            font-weight: bold;
            color: #0d47a1;
            border: 1px solid #90caf9;
            text-align: center;
        }
        .timer {
            font-size: 14px;
            color: #e63946;
            margin-top: 5px;
        }
        .controls {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 10px;
            margin-top: 15px;
        }
        button, select, input {
            padding: 12px;
            border: none;
            border-radius: 6px;
            font-size: 16px;
            cursor: pointer;
            transition: all 0.2s ease;
        }
        button {
            background-color: #1976d2;
            color: white;
        }
        button:hover {
            background-color: #1565c0;
        }
        input, select {
            background: #fff;
            border: 1px solid #ccc;
            width: 200px;
        }
        #lang-select {
            position: absolute;
            top: 25px;
            right: 25px;
            background: #fff;
            border: 1px solid #ccc;
            padding: 8px;
        }
        #inbox-container {
            margin-top: 20px;
            max-height: 400px;
            overflow-y: auto;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 8px;
            background: #fafafa;
        }
        .email-item {
            padding: 15px;
            border-bottom: 1px solid #ddd;
            background: white;
            border-radius: 8px;
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
            margin-bottom: 15px;
        }
        .email-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }
        .email-from {
            font-size: 14px;
            color: #666;
        }
        .email-time {
            font-size: 12px;
            color: #999;
        }
        .email-subject {
            font-size: 16px;
            font-weight: bold;
            color: #333;
            margin: 5px 0;
        }
        .email-body {
            font-size: 14px;
            color: #444;
            white-space: pre-wrap;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1 id="title">Temporary Email Service</h1>
        <select id="lang-select" onchange="changeLanguage(this.value)">
            <option value="en">English</option>
            <option value="zh-CN">简体中文</option>
            <option value="zh-TW">繁體中文</option>
            <option value="it">Italiano</option>
            <option value="es">Español</option>
        </select>
        <div class="email-section">
            <div class="email-box" id="email-box">
                <span id="email"></span>
                <div class="timer" id="timer"></div>
            </div>
            <button onclick="copyEmail()" id="copy-btn">Copy</button>
            <button onclick="extendEmail()" id="extend-btn">Extend 10 Minutes</button>
        </div>
        <div class="controls">
            <input id="prefix-input" type="text" placeholder="Custom Prefix (Optional)">
            <select id="domain-select">
                <option value="" id="domain-select-default"></option>
            </select>
            <input id="domain-input" type="text" placeholder="Or Enter Custom Domain">
            <button onclick="getNewEmail()" id="generate-btn">Generate New Email</button>
            <button onclick="fetchEmails()" id="refresh-btn">Refresh</button>
        </div>
        <div id="inbox-container">
            <div id="inbox"></div>
        </div>
        <div class="intro">
            <h2 id="intro-title"></h2>
            <p id="intro-content"></p>
        </div>
        <div class="tutorial">
            <h2 id="tutorial-title"></h2>
            <div id="tutorial-content"></div>
        </div>
    </div>

    <script>
        let currentEmail = '';
        let translations = {};
        let currentLang = 'en';
        let expirationTime = 0;
        let mainDomain = '';
        const supportedLangs = ['en', 'zh-CN', 'zh-TW', 'it', 'es'];

        function detectBrowserLanguage() {
            const browserLang = navigator.language || navigator.userLanguage;
            console.log('Detected browser language:', browserLang); // 调试:输出浏览器语言
            if (!browserLang) return 'en';

            const langLower = browserLang.toLowerCase();
            if (langLower.startsWith('zh-cn')) return 'zh-CN';
            if (langLower.startsWith('zh-tw') || langLower.startsWith('zh-hk')) return 'zh-TW';
            if (langLower.startsWith('zh')) return 'zh-CN'; // 默认简体中文
            if (langLower.startsWith('it')) return 'it';
            if (langLower.startsWith('es')) return 'es';
            return 'en'; // 默认回退到英文
        }

        async function loadTranslations(lang) {
            try {
                const response = await fetch(`/languages/${lang}.json`);
                if (!response.ok) throw new Error(`Failed to load ${lang}.json`);
                translations[lang] = await response.json();
                console.log(`Loaded translations for ${lang}`); // 调试:确认加载成功
            } catch (err) {
                console.error(`Error loading ${lang}.json: ${err}`);
                translations[lang] = translations['en']; // 回退到英文
            }
        }

        async function initLanguages() {
            // 先加载所有语言文件
            await Promise.all(supportedLangs.map(lang => loadTranslations(lang)));
            // 检测浏览器语言并设置
            currentLang = detectBrowserLanguage();
            console.log('Initial language set to:', currentLang); // 调试:输出初始语言
            document.getElementById('lang-select').value = currentLang;
            updateUI();
        }
        initLanguages();

        async function loadMainDomain() {
            const response = await fetch('/api/main-domain');
            const data = await response.json();
            mainDomain = data.domain;
            document.title = `Temp Email - ${mainDomain}`;
        }
        loadMainDomain();

        async function loadDomains() {
            const response = await fetch('/api/domains');
            const domains = await response.json();
            const select = document.getElementById('domain-select');
            const defaultOption = document.getElementById('domain-select-default');
            defaultOption.textContent = translations[currentLang].domainSelect;
            domains.forEach(domain => {
                const option = document.createElement('option');
                option.value = domain;
                option.textContent = domain;
                select.appendChild(option);
            });
            select.value = '';
            getNewEmail();
        }
        loadDomains();

        function changeLanguage(lang) {
            currentLang = lang || document.getElementById('lang-select').value;
            if (!supportedLangs.includes(currentLang)) {
                currentLang = 'en';
            }
            console.log('Language changed to:', currentLang); // 调试:输出切换后的语言
            document.getElementById('lang-select').value = currentLang;
            if (translations[currentLang]) updateUI();
        }

        function updateUI() {
            const t = translations[currentLang] || translations['en'];
            document.getElementById('title').textContent = t.title;
            document.getElementById('generate-btn').textContent = t.generateBtn;
            document.getElementById('refresh-btn').textContent = t.refreshBtn;
            document.getElementById('extend-btn').textContent = t.extendBtn;
            document.getElementById('copy-btn').textContent = t.copyBtn;
            document.getElementById('prefix-input').placeholder = t.prefixPlaceholder;
            document.getElementById('domain-input').placeholder = t.domainPlaceholder;
            document.getElementById('intro-title').textContent = t.introTitle;
            document.getElementById('intro-content').textContent = t.introContent;
            document.getElementById('tutorial-title').textContent = t.tutorialTitle;
            document.getElementById('tutorial-content').innerHTML = t.tutorialContent;
            document.getElementById('domain-select-default').textContent = t.domainSelect;
            fetchEmails();
        }

        async function getNewEmail() {
            try {
                let domain = document.getElementById('domain-input').value.trim();
                if (!domain) {
                    domain = document.getElementById('domain-select').value || '';
                }
                const prefix = document.getElementById('prefix-input').value.trim();
                const url = `/api/new-email?domain=${encodeURIComponent(domain)}${prefix ? `&prefix=${encodeURIComponent(prefix)}` : ''}`;
                const response = await fetch(url);
                const data = await response.json();
                if (data.error) throw new Error(data.error);
                currentEmail = data.email;
                expirationTime = data.expiresAt;
                document.getElementById('email').textContent = currentEmail;
                updateTimer();
                fetchEmails();
            } catch (err) {
                console.error('Error:', err);
                alert((translations[currentLang] || translations['en']).error + ': ' + err.message);
            }
        }

        async function extendEmail() {
            if (!currentEmail) return;
            try {
                const response = await fetch(`/api/extend/${encodeURIComponent(currentEmail)}`);
                const data = await response.json();
                if (data.error) throw new Error(data.error);
                expirationTime = data.expiresAt;
                updateTimer();
            } catch (err) {
                console.error('Error extending email:', err);
                alert((translations[currentLang] || translations['en']).error + ': ' + err.message);
            }
        }

        function copyEmail() {
            if (!currentEmail) {
                alert((translations[currentLang] || translations['en']).noEmail);
                return;
            }
            if (navigator.clipboard && navigator.clipboard.writeText) {
                navigator.clipboard.writeText(currentEmail)
                    .then(() => {
                        alert((translations[currentLang] || translations['en']).copied);
                    })
                    .catch(err => {
                        console.error('Clipboard API failed:', err);
                        fallbackCopyEmail();
                    });
            } else {
                fallbackCopyEmail();
            }
        }

        function fallbackCopyEmail() {
            const tempInput = document.createElement('input');
            tempInput.value = currentEmail;
            document.body.appendChild(tempInput);
            tempInput.select();
            try {
                document.execCommand('copy');
                alert((translations[currentLang] || translations['en']).copied);
            } catch (err) {
                console.error('Fallback copy failed:', err);
                alert((translations[currentLang] || translations['en']).copyFailed);
            }
            document.body.removeChild(tempInput);
        }

        function updateTimer() {
            const now = Date.now();
            const timeLeft = expirationTime - now;
            const timer = document.getElementById('timer');
            if (timeLeft > 0) {
                const minutes = Math.floor(timeLeft / 60000);
                const seconds = Math.floor((timeLeft % 60000) / 1000);
                timer.textContent = `${translations[currentLang].timeLeft}: ${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
            } else {
                timer.textContent = translations[currentLang].expired;
                currentEmail = '';
                document.getElementById('email').textContent = '';
            }
        }
        setInterval(updateTimer, 1000);

        async function fetchEmails() {
            if (!currentEmail || Date.now() > expirationTime) return;
            try {
                const response = await fetch(`/api/emails/${encodeURIComponent(currentEmail)}`);
                const data = await response.json();
                const inbox = document.getElementById('inbox');
                const t = translations[currentLang] || translations['en'];
                inbox.innerHTML = data.emails.map(e => `
                    <div class="email-item">
                        <div class="email-header">
                            <span class="email-from">${t.from}: ${e.from}${e.fromName ? ` (${e.fromName})` : ''}</span>
                            <span class="email-time">${t.received}: ${e.receivedAt}</span>
                        </div>
                        <div class="email-subject">${e.subject}</div>
                        <div class="email-body">${e.body}</div>
                    </div>
                `).join('');
                expirationTime = data.expiresAt;
            } catch (err) {
                console.error('Error fetching emails:', err);
            }
        }

        setInterval(fetchEmails, 5000);
    </script>
</body>
</html>

2.5 创建语言文件

public/languages/ 目录下创建以下文件:
zh-CN.json
{
  "title": "临时邮箱服务",
  "generateBtn": "生成新邮箱",
  "refreshBtn": "刷新",
  "prefixPlaceholder": "自定义前缀(可选)",
  "domainPlaceholder": "或输入自定义域名",
  "emailLabel": "您的邮箱:",
  "from": "发件人",
  "received": "接收时间",
  "error": "错误",
  "timeLeft": "剩余时间",
  "expired": "已过期",
  "extendBtn": "延长10分钟",
  "copyBtn": "复制",
  "copied": "已复制",
  "noEmail": "没有邮箱可复制",
  "copyFailed": "复制失败"
}
en.json
{
  "title": "Temporary Email Service",
  "generateBtn": "Generate New Email",
  "refreshBtn": "Refresh",
  "prefixPlaceholder": "Custom prefix (optional)",
  "domainPlaceholder": "Or enter custom domain",
  "emailLabel": "Your email:",
  "from": "From",
  "received": "Received",
  "error": "Error",
  "timeLeft": "Time left",
  "expired": "Expired",
  "extendBtn": "Extend 10 minutes",
  "copyBtn": "Copy",
  "copied": "Copied",
  "noEmail": "No email to copy",
  "copyFailed": "Copy failed"
}
zh-TW.json
{
  "title": "臨時郵箱服務",
  "generateBtn": "生成新郵箱",
  "refreshBtn": "刷新",
  "prefixPlaceholder": "自定義前綴(可選)",
  "domainPlaceholder": "或輸入自定義域名",
  "emailLabel": "您的郵箱:",
  "from": "發件人",
  "received": "接收時間",
  "error": "錯誤",
  "timeLeft": "剩餘時間",
  "expired": "已過期",
  "extendBtn": "延長10分鐘",
  "copyBtn": "複製",
  "copied": "已複製",
  "noEmail": "沒有郵箱可複製",
  "copyFailed": "複製失敗"
}
it.json
{
  "title": "Servizio Email Temporaneo",
  "generateBtn": "Genera Nuova Email",
  "refreshBtn": "Aggiorna",
  "prefixPlaceholder": "Prefisso personalizzato (opzionale)",
  "domainPlaceholder": "O inserisci dominio personalizzato",
  "emailLabel": "La tua email:",
  "from": "Da",
  "received": "Ricevuto",
  "error": "Errore",
  "timeLeft": "Tempo rimanente",
  "expired": "Scaduto",
  "extendBtn": "Estendi 10 minuti",
  "copyBtn": "Copia",
  "copied": "Copiato",
  "noEmail": "Nessuna email da copiare",
  "copyFailed": "Copia fallita"
}
es.json
{
  "title": "Servicio de Correo Temporal",
  "generateBtn": "Generar Nuevo Correo",
  "refreshBtn": "Actualizar",
  "prefixPlaceholder": "Prefijo personalizado (opcional)",
  "domainPlaceholder": "O ingresa dominio personalizado",
  "emailLabel": "Tu correo:",
  "from": "De",
  "received": "Recibido",
  "error": "Error",
  "timeLeft": "Tiempo restante",
  "expired": "Expirado",
  "extendBtn": "Extender 10 minutos",
  "copyBtn": "Copiar",
  "copied": "Copiado",
  "noEmail": "No hay correo para copiar",
  "copyFailed": "Copia fallida"
}

步骤 3:配置域名和 HTTPS
3.1 更新域名
编辑 index.js,替换为主域名和固定域名:
const MAIN_DOMAIN = 'email.yourdomain.com'; // 你的主域名
const FIXED_DOMAINS = ['yourdomain1.com', 'yourdomain2.com', 'yourdomain3.com']; // 你的固定域名
3.2 配置 DNS
  1. 主域名
    • A 记录:email.yourdomain.com -> <服务器 IP>
    • 示例:
      email.yourdomain.com  A  123.45.67.89
  2. 固定域名 MX 记录
    • MX 记录:yourdomain1.com -> <服务器 IP>
    • 示例:
      @  MX  10  123.45.67.89
    • 对每个固定域名重复此步骤。
  3. 其他域名
    • 用户可自行配置 MX 记录指向服务器 IP。
3.3 配置 HTTPS
  1. 生成证书:
    sudo certbot --nginx -d email.yourdomain.com
    • 按照提示完成配置,选择重定向 HTTP 到 HTTPS。
  2. 将证书复制到项目目录(可选,若使用 Node.js HTTPS):
    mkdir cert
    cp /etc/letsencrypt/live/email.yourdomain.com/fullchain.pem cert/server.crt
    cp /etc/letsencrypt/live/email.yourdomain.com/privkey.pem cert/server.key

步骤 4:配置 Nginx
创建 Nginx 配置文件:
sudo nano /etc/nginx/sites-available/email.yourdomain.com
输入以下内容:
server {
    listen 80;
    server_name email.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name email.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/email.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/email.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}
启用并重启 Nginx:
sudo ln -s /etc/nginx/sites-available/email.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

步骤 5:运行程序
5.1 安装依赖
npm install
5.2 使用 PM2 启动
sudo pm2 start index.js --name "temp-email"
pm2 startup  # 按照提示运行输出的命令
pm2 save
pm2 stop temp-email # 停止
5.3 开放端口
sudo ufw allow 25
sudo ufw allow 80
sudo ufw allow 443
sudo ufw status

步骤 6:测试
  1. 访问程序
    • 打开浏览器,访问 https://email.yourdomain.com
    • 确认页面加载,下拉菜单显示固定域名。
  2. 生成邮箱
    • 选择固定域名(如 yourdomain1.com)或输入自定义域名(如 customdomain.com)。
    • 点击“生成新邮箱”,查看生成的邮箱地址。
  3. 发送邮件
    • 配置自定义域名的 MX 记录指向服务器 IP。
    • 发送邮件到生成的邮箱地址,检查收件箱是否收到。
  4. 功能测试
    • 复制:点击“复制”按钮,粘贴到其他地方验证。
    • 延长:点击“延长10分钟”,确认倒计时更新。
    • 语言:切换语言,确认界面翻译正确。

注意事项
  • DNS 传播:MX 记录生效可能需要几分钟到 48 小时。
  • 安全性:开放所有域名可能接收垃圾邮件,可考虑添加黑名单。
  • 证书续订:Let’s Encrypt 证书有效期 90 天,运行以下命令测试续订:
    sudo certbot renew --dry-run

故障排查
  • 无法访问 HTTPS
    • 检查证书路径:ls -l /etc/letsencrypt/live/email.yourdomain.com/
    • 检查 Nginx 日志:sudo tail -f /var/log/nginx/error.log
  • 邮件未收到
    • 确认 MX 记录:nslookup -type=MX yourdomain1.com
    • 检查 SMTP 端口:sudo netstat -tuln | grep 25
  • PM2 未运行
    • 查看状态:pm2 list
    • 查看日志:pm2 logs temp-email

扩展
  • 添加域名验证:在 onRcptTo 中校验域名,防止滥用。
  • 优化存储:使用数据库(如 SQLite)替换文件存储。
  • 界面美化:调整 CSS 样式或使用前端框架。
  • ps aux | grep node
    
    lsof -i :3000
    kill -9 12345

原创文章,作者:开空网,转载请注明出处:https://www.openull.org/temp-mail.html

(0)
上一篇 10 3 月, 2025 7:20 下午
下一篇 18 3 月, 2021 3:27 下午

相关推荐

  • T-Drive 基于Telegram 免费、安全且无限容量云端储存空间

    如果有公司想销售无限容量云端硬碟或云端储存空间,绝大多数都不是真正的「无限制」,或是以各种方式来限制使用者的使用行为,毕竟储存服务需要各项成本,若无限制使用可能没有一家公司可以支撑…

    11 3 月, 2022
    1.2K
  • “编程随想”今何在?

    时至今日,已经有半年的时间,编程随想的博客未更新博文内容了,盯着他的博客上的思想者,我也不由得感慨万千。具体从什么时候开始看编程随想的博客,已经记不得详细的时间了,只是觉得其博客条…

    11 11 月, 2021
    419
  • Fastly 免费CDN套餐-200G流量

    网址:https://www.fastly.com/pricing/ Fastly,美国云计算服务商,其边缘运算平台提供内容分发网络、网络安全服务、负载均衡及视频流等服务。Fast…

    24 6 月, 2024
    554
  • Facebook对messenger增加音视频聊天的端对端加密功能

    Facebook在8月13号宣布,它正在向 Facebook Messenger 推出对端到端加密视频通话和音频通话的支持。该公司还计划对 Instagram 的消息功能进行更改,…

    16 8 月, 2021
    262
  • 免费薅Perplexity Pro一年定阅,价格200美元

    Perplexity AI 是一种创新的对话式搜索引擎,它结合了人工智能技术和深度搜索功能,旨在提高信息检索的效率。与传统搜索引擎不同,Perplexity AI 能够理解和回应模…

    7 9 月, 2024
    940
  • 什么是DoH?Macos/Windows怎么开启DoH?

    什么是DNS? DNS全称是Domain Name System,中文译为域名系统,是互联网的一项服务,它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网…

    3 7 月, 2021
    872
  • Google Play 上经过独立安全测试的VPN应用列表

    Google Play在11月2号推出了一项新举措,通过透明标签和独立安全测试来提升应用的安全和隐私,特别针对处理敏感数据的VPN应用。通过移动应用安全评估(MASA)项目,VPN…

    15 1 月, 2024
    691
  • 老王VPN怎么样?是不是钓鱼VPN?

    对于各位热衷于翻墙查找信息的各位而言,老王VPN应该不陌生,随手在Google Play上一搜索“中文VPN”,老王VPN都会排在比较靠前的位置。并且其在安卓端的安装量已经有百万之…

    29 3 月, 2021
    1.1K
  • 国外可用的虚拟手机号大全

    1. Google Voice Google Voice是由Google 推出的VOIP 服务,需要使用真实的美国手机号进行注册,一旦注册成功就能永久免费使用,同时提供许多加值服务…

    2 9 月, 2021
    394
  • NordVPN-2024年最值得购买的顶级VPN

    NordVPN 公司背景 NordVPN公司设立于巴拿马,巴拿马法律中没有强制性的数据保留,也非五眼或十四眼联盟。NordVPN是由NordSecurity开发,这是一家由Tom …

    15 6 月, 2024
    514