搭建SMTP服务及邮件接收


介绍

SMTP是一个电子邮件传输协议,通常用于邮件的发送,主要使用TCP:25端口,下面示例如何搭建一个简单的SMTP邮件服务,尝试接收来自任何邮件地址的发件,并定义为anyMail!

域名及DNS

1.首先需要准备一个域名作为你的邮箱后缀,域名可以去各大云厂商或其它途径购买,如giveme.com,同时也需要一台服务器且拥有公网IP,如44.55.66.77

2.还需要去域名托管商设置域名的DNS解析记录,如果你想你的邮箱地址比如是yourktop@giveme.com,那么添加两条条记录如下
@ A 44.55.66.77
@ MX giveme.com.
这里第一条记录各个部分的意思是,设置主机记录为@,记录类型为A记录,记录值为44.55.66.77,指将giveme.com解析到44.55.66.77这个IP上
第二条记录的意思是,设置主机记录为@,记录类型为MX记录,记录值为giveme.com,指设置邮件服务解析,地址为giveme.com
如下
DNS解析设置

CODE

1.后端js,使用了mailparser、smtp-server等模块,注意放开对应TCP端口,这里返回的是带有html格式的内容,可以修改代码返回纯文本或其它格式内容,也可以在这里写个过滤,只接收关于如xxx注册验证的邮件
2.也可以去github查看源码

const { simpleParser } = require('mailparser');
const SMTPserver = require('smtp-server').SMTPServer;
const express = require('express');
const app = express();
const http = require('http');
const httpServer = http.createServer(app);
const { Server } = require('socket.io');
const io = new Server(httpServer);
const cors = require('cors');

app.use(cors());
app.use(express.static('./public'));

io.on('connection', socket => {
    console.log("有靓仔|靓妹已接入!");
    io.emit('msg', '您已成功接入宇宙霹雳无敌超级大爆炸螺旋托马斯回转跳跃上升牛逼的破网站! 👍--> https://npmcow.com')
})

const server = new SMTPserver({
    host: '0.0.0.0',
    port: 25,
    //启用SMTP认证
    authOptional: true,
    onData(stream, session, callback) {
        let emailData = '';
        stream.on('data', (chunk) => {
            emailData += chunk;
        });
        stream.on('end', async () => {
            // 解析邮件内容
            const parsed = await simpleParser(emailData);
            console.log(parsed)
            const date = parsed.date;
            const from = parsed.from.value[0].address;
            const subject = parsed.subject;
            // const attachments = parsed.attachments[0].content;   //一般附件内容,二进制数据
            const mailContent = parsed.html;//邮件正文,更复杂的逻辑需要单独处理,如正则匹配提取验证码和url等
            console.log(mailContent);
            io.emit('mailContent', mailContent)//发送到前端页面
            callback();
        });
    },
});

server.listen(25, () => {
    console.log('SMTP server is listening on port', server.server.address().port);
});

httpServer.listen(4111, () => {
    console.log('HTTP server listening on port 4111')
})

2.前端h5,一个简单的页面,包含邮箱地址、一些名称和邮箱收件情况,socket.io可下载下来,也可直接使用CDN链接

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AnyMail system</title>
    <link rel="stylesheet" href="./css/main.css" />
</head>

<body>
    <div class="container">
        <div class="card">
            <div class="card-header">
                <h5>Get Email Content,获取你的任何邮件内容!</span>
                    <br>
                    使用随机邮箱,自动获取邮件正文html信息返回,自行提取
                </h5>
            </div>
            <div class="card-body">
                <div class="form-group">
                    <label>Email</label>
                    <input type="email" autocomplete="off" readonly id="email-input" />
                </div>
                <div class="form-generate">
                    <label>用户名</label>
                    <input type="text" id="username-input" placeholder="用于生成用户名" />
                </div>
                <div class="form-generate">
                    <label>公司名</label>
                    <input type="text" id="corname-input" placeholder="用于生成公司名称" />
                </div>
                <div class="form-generate">
                    <label>邮件内容</label>
                    <div id="mail-area" contenteditable="true"></div>
                </div>
                <!-- <div class="form-generate">
                    <label>邮件内容</label>
                    <textarea id="mails-area"></textarea>
                </div> -->
                <button class="btn btn-primary" id="get-email-btn">获取邮箱</button>
                <button class="btn btn-primary" id="copy-email-btn">复制邮箱</button>
                <button class="btn btn-primary" id="refresh-name-btn">刷新名称</button>
                <br /><br />
            </div>
        </div>
        <div class="alert" id="alert" style="display: none"></div>
    </div>
</body>
<script src="./js/script.js"></script>
<script src="./js/socket.io.min.js"></script>

<script>
    const textarea = document.getElementById('mail-area');
    const socket = io();
    socket.on('msg', msg => {
        console.log(msg);
    })
    socket.on('mailContent', msg => {
        console.log(msg)
        textarea.innerHTML = msg
    })
</script>

</html>

2.css样式

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
    font-size: 16px;
    line-height: 1.5;
}

.container {
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
}

.card {
    border: 1px solid #ddd;
    border-radius: 4px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.card-header {
    background-color: #f0f0f0;
    padding: 10px;
}

.card-body {
    padding: 20px;
}

.form-group,
.form-generate {
    margin-bottom: 20px;
}

/* #mail-area {
    width: 100%;
    height: 200px;
    font-size: 20px;
    border: 1px solid #ccc;
    padding: 10px;
    background-color: #fff;
    font-size: 14px;
    line-height: 1.5;
    color: #333;
    resize: none;
} */


div[contenteditable="true"] {
    width: 100%;
    height: 200px;
    font-size: 20px;
    border: 1px solid #ccc;
    padding: 10px;
    background-color: #fff;
    font-size: 14px;
    line-height: 1.5;
    color: #333;
    overflow: auto;
}



#form-generate textarea {
    width: 100%;
    box-sizing: border-box;
}


label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
}

input[type="email"],
input[type="password"],
input[type="text"] {
    width: 100%;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
}

.btn {
    display: inline-block;
    padding: 10px 20px;
    margin-bottom: 0;
    font-size: 16px;
    font-weight: 400;
    line-height: 1.5;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    cursor: pointer;
    border: 1px solid transparent;
    border-radius: 4px;
    background-color: #007bff;
    color: #fff;
}

.btn-primary {
    background-color: #007bff;
}

.btn-primary:hover {
    background-color: #0069d9;
}

.alert {
    padding: 10px;
    margin-bottom: 20px;
    border: 1px solid transparent;
    border-radius: 4px;
    transition: opacity 0.5s;
}

.alert-danger {
    color: #721c24;
    background-color: #f8d7da;
    border-color: #f5c6cb;
}

.alert-success {
    color: #155724;
    background-color: #d4edda;
    border-color: #c3e6cb;
}

3.js代码,懒得删掉先前的版本遗留代码了,不过不影响

const getEmailBtn = document.getElementById("get-email-btn");
const getEmailUrlBtn = document.getElementById("get-url-btn");
const copyEmailBtn = document.getElementById('copy-email-btn');
const emailInput = document.getElementById("email-input");
const passwordInput = document.getElementById("password-input");
const userNameInput = document.getElementById("username-input");
const corNameInput = document.getElementById("corname-input");
const alertDiv = document.getElementById("alert");
const refreshNameBtn = document.getElementById("refresh-name-btn");


async function getEmailInfo2() {
    //黑名单前缀,后端也会同步设置,算了,不设置了
    const blackList = ["admin", "pengju", "fuck", "you", "postmaster", "system", "webmaster", "administrator", "hostmaster", "service", "server", "root"]
    const email = (Math.random().toString(24) + '@giveme.com').substring(2);
    if (blackList.includes(email.split('@')[0])) {
        showErrorAlert('Not allowed,please try again');
        return;
    }
    emailInput.value = email
    showSuccessAlert('Successfully generated email address');
}

//复制获取到的email
async function copyEmail() {
    if (emailInput.value == '') {
        showErrorAlert('Email is empty');
        return;
    }
    try {
        await navigator.clipboard.writeText(emailInput.value);
        showSuccessAlert('copy succeeded');
    } catch (e) {
        showErrorAlert('copy failed,may not support');
    }
}

async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}


//生成随机名称
function generateTwoName() {
    const f = ['James', 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez', 'Hernandez', 'Ford'];
    const g = 'abcdefghijklmnopqrstuvwxyz';
    let result = '';
    for (let i = 1; i <= Math.floor(Math.random() * (10 - 5) + 5); i++) {
        result += g[Math.floor(Math.random() * g.length)];
    }
    const name = f[Math.floor(Math.random() * (f.length))] + ' ' + result[0].toUpperCase() + result.slice(1);

    const a = ['Gilead Sciences', 'EMC', 'ExxonMobil', 'Inte', 'Symantec', 'Amgen', 'Chevron', 'Cisco Systems', 'eBay', 'Apple', 'NetApp', 'Microsoft', 'Facebook', 'Salesforce.com', 'Google']
    const cname = a[Math.floor(Math.random() * a.length)];
    userNameInput.value = name;
    corNameInput.value = cname;
}
generateTwoName();


// 定义显示成功提示的函数
async function showSuccessAlert(message) {
    alertDiv.classList.remove("alert-danger");
    alertDiv.classList.add("alert-success");
    alertDiv.innerText = message;
    alertDiv.style.display = "block";

    // await sleep(4000);
    // alertDiv.style.opacity = 0;
    // alertDiv.addEventListener('transitionend', () => {
    //     alertDiv.style.display = 'none';
    //     alertDiv.style.opacity = 1;
    // });
}

// 定义显示错误提示的函数
async function showErrorAlert(message) {
    alertDiv.classList.remove("alert-success");
    alertDiv.classList.add("alert-danger");
    alertDiv.innerText = message;
    alertDiv.style.display = "block";

    // await sleep(4000);
    // alertDiv.style.opacity = 0;
    // alertDiv.addEventListener('transitionend', () => {
    //     alertDiv.style.display = 'none';
    //     alertDiv.style.opacity = 1;
    // });
}



getEmailBtn.addEventListener("click", getEmailInfo2);
copyEmailBtn.addEventListener('click', copyEmail);
refreshNameBtn.addEventListener('click', generateTwoName);

4.页面如下所示
h5页面

部署

1.服务端安装完相应模块后,使用node或pm2托管,pm2 start index.js --name anyMail -l run.log

2.前端页面点击生成或者随便输入一个后缀为@giveme.com的邮箱地址作为收件地址即可,收件如下

收件情况

nginx设置

1.如果你是泛域名证书,则如下所示

server {
    listen 80;
    server_name anymail.giveme.com;
    location / {
        index  index.html;
        try_files  $uri $uri/ /index.html;
        return 301 https://$server_name$request_uri;
    }   
}
server {
    listen 443 ssl;
    server_name anymail.giveme.com;
    
    location / {
        proxy_pass  http://127.0.0.1:4111;
    }
    ssl_certificate /var/www/ssl/fullchain.cer;
    ssl_certificate_key /var/www/ssl/npmcow.com.key;

    #enables all versions of TLS, but not SSLv2 or 3 which are weak and now deprecated.
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    #Disables all weak ciphers
    ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
    ssl_prefer_server_ciphers on;
}

文章作者: 牛牛🐮
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 牛牛🐮 !
  目录