news 2026/10/8 22:50:50

自己编写邮件服务器和客户端

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自己编写邮件服务器和客户端

1. 引言

电子邮件是互联网上最基础也最常用的应用之一。虽然今天我们大多使用现成的邮件服务(如 Gmail、Outlook、企业邮箱),但亲手编写一套邮件服务器和客户端,能让你深入理解邮件从发送到接收的完整链路,掌握 SMTP、POP3/IMAP 等核心协议的工作原理。

本文将从零开始,用 Python 编写一个可运行的邮件服务器和客户端。服务器负责接收和存储邮件,客户端负责发送和收取邮件。我们会先介绍邮件系统的基本架构,再逐步实现服务器端和客户端,最后给出完整的测试流程。

2. 邮件系统基础架构

在动手写代码之前,先了解邮件系统的基本组成。一个完整的邮件系统通常包含以下几个角色:

  • 邮件用户代理(MUA):即邮件客户端,负责撰写、发送和阅读邮件。

  • 邮件传输代理(MTA):即邮件服务器,负责邮件的路由和转发。

  • 邮件投递代理(MDA):可以把它理解为「邮局里的分拣员」。当 MTA 把邮件送到收件人所在的服务器后,MDA 负责把邮件放进收件人自己的「信箱」里——也就是写入收件人在服务器上的邮箱存储目录。在本文的示例中,服务器收到DATA命令后把邮件保存到mailboxes/bob@example.com/目录,这一步就是 MDA 的职责。邮件发送和接收主要依赖以下协议:

  • SMTP(简单邮件传输协议):用于客户端向服务器发送邮件,以及服务器之间转发邮件,默认端口 25。

  • POP3(邮局协议第 3 版):用于客户端从服务器下载邮件,默认端口 110。

  • IMAP(互联网消息访问协议):用于客户端在线管理服务器上的邮件,默认端口 143。

本文的示例将实现一个简化版 SMTP 服务器和一个支持 SMTP 发送、POP3 接收的客户端。

3. 环境准备

在开始之前,请确保你的开发环境满足以下要求:

  • Python 3.8 及以上版本
  • 无需安装任何第三方库,全部使用 Python 标准库实现

你可以先创建一个项目目录,并在其中新建以下文件:

mail_project/ ├── server.py # 邮件服务器 ├── client.py # 邮件客户端 └── mailboxes/ # 邮件存储目录(服务器自动创建)

4. 实现邮件服务器

邮件服务器的核心职责是:监听 SMTP 端口,接收客户端发来的邮件,解析邮件内容,并将邮件保存到对应收件人的邮箱目录中。

4.1 服务器主框架

服务器启动后监听 2525 端口,等待客户端连接。当客户端连上后,服务器先发送欢迎信息,然后进入命令循环,逐行读取客户端发来的命令并作出响应。

下面用类似 telnet 的方式展示服务器启动后,客户端连接并交互的完整流程(S:表示服务器响应,C:表示客户端发送的命令):

$ python server.py 邮件服务器已启动,监听端口 2525 # 客户端连接 S: 220 Welcome to My Mail Server # 客户端问候服务器,开始会话 C: HELO alice S: 250 Hello # 指定发件人地址 C: MAIL FROM:<alice@example.com> S: 250 OK # 指定收件人地址 C: RCPT TO:<bob@example.com> S: 250 OK # 开始传输邮件正文 C: DATA S: 354 End with <CR><LF>.<CR><LF> # 邮件正文内容(以单独一行的 "." 结束) C: From: alice@example.com C: To: bob@example.com C: Subject: 你好,这是测试邮件 C: C: 这是一封来自自建邮件服务器的测试邮件。 C: . S: 250 Message accepted # 结束会话 C: QUIT S: 221 Bye

4.2 处理 SMTP 命令

从上面的交互流程可以看到,SMTP 会话由一系列命令组成。我们至少需要实现以下命令:

  • HELO/EHLO:客户端问候服务器,开始会话。
  • MAIL FROM:指定发件人地址。
  • RCPT TO:指定收件人地址。
  • DATA:开始传输邮件正文内容。
  • QUIT:结束会话。

服务器收到DATA命令后进入数据模式,此时客户端发送的所有行都被当作邮件正文收集起来,直到收到单独一行的.才结束。随后服务器把邮件保存到收件人的邮箱目录中,并返回250 Message accepted。

如果客户端发送了无法识别的命令,服务器会返回500 Command unrecognized;如果客户端在数据模式下发送了空行或非法内容,服务器也会给出相应的错误响应。

5. 实现邮件客户端

邮件客户端需要完成两件事:通过 SMTP 发送邮件,通过 POP3 接收邮件。下面同样用类似 telnet 的方式展示客户端与服务器之间的交互流程。

5.1 发送邮件

客户端通过 SMTP 协议向服务器发送邮件。整个交互流程如下(S:表示服务器响应,C:表示客户端发送的命令):

# 客户端连接服务器 S: 220 Welcome to My Mail Server # 客户端问候服务器,开始会话 C: HELO alice S: 250 Hello # 指定发件人地址 C: MAIL FROM:<alice@example.com> S: 250 OK # 指定收件人地址 C: RCPT TO:<bob@example.com> S: 250 OK # 开始传输邮件正文 C: DATA S: 354 End with <CR><LF>.<CR><LF> # 邮件正文内容(以单独一行的 "." 结束) C: From: alice@example.com C: To: bob@example.com C: Subject: 你好,这是测试邮件 C: C: 这是一封来自自建邮件服务器的测试邮件。 C: . S: 250 Message accepted # 结束会话 C: QUIT S: 221 Bye

5.2 接收邮件

客户端通过 POP3 协议从服务器接收邮件。整个交互流程如下(S:表示服务器响应,C:表示客户端发送的命令):

# 客户端连接服务器 S: +OK POP3 server ready # 客户端发送用户名进行认证 C: USER bob@example.com S: +OK User accepted # 客户端发送密码完成认证 C: PASS 123456 S: +OK Mailbox locked and ready # 客户端列出邮箱中的邮件 C: LIST S: +OK 1 messages S: 1 320 S: . # 客户端读取第 1 封邮件 C: RETR 1 S: +OK message follows S: From: alice@example.com S: To: bob@example.com S: Subject: 你好,这是测试邮件 S: S: 这是一封来自自建邮件服务器的测试邮件。 S: . # 客户端结束会话 C: QUIT S: +OK Bye

6. 扩展:增加 POP3 服务器支持

上面的客户端示例中,接收邮件依赖一个 POP3 服务器。为了让整套系统完全自包含,我们可以在服务器端也实现一个简单的 POP3 服务。POP3 服务器的交互流程如下(S:表示服务器响应,C:表示客户端发送的命令):

# 客户端连接服务器 S: +OK POP3 server ready # 客户端发送用户名进行认证 C: USER bob@example.com S: +OK User accepted # 客户端发送密码完成认证 C: PASS 123456 S: +OK Mailbox locked and ready # 客户端列出邮箱中的邮件 C: LIST S: +OK 2 messages S: 1 320 S: 2 280 S: . # 客户端读取第 1 封邮件 C: RETR 1 S: +OK message follows S: From: alice@example.com S: To: bob@example.com S: Subject: 你好,这是测试邮件 S: S: 这是一封来自自建邮件服务器的测试邮件。 S: . # 客户端读取不存在的邮件 C: RETR 99 S: -ERR no such message # 客户端结束会话 C: QUIT S: +OK Bye

POP3 服务器需要实现的核心命令包括:

  • USER <用户名>:客户端发送用户名进行认证。
  • PASS <密码>:客户端发送密码完成认证。
  • LIST:列出邮箱中的邮件编号和大小。
  • RETR <编号>:读取指定编号的邮件内容。
  • QUIT:结束会话。

7. 测试流程

完成以上设计后,按以下步骤进行测试:

  1. 启动服务器:
python server.py
  1. 在另一个终端中,用 telnet 或 nc 连接服务器,模拟客户端发送邮件:
telnet127.0.0.12525

连接后,按照第 4 节展示的交互流程,依次输入HELO、MAIL FROM、RCPT TO、DATA、邮件正文和.,最后输入QUIT结束会话。

  1. 查看服务器端日志,确认邮件已保存到mailboxes/bob@example.com/目录。

  2. 在另一个终端中,用 telnet 或 nc 连接 POP3 服务器,模拟客户端接收邮件:

telnet127.0.0.11110

连接后,按照第 5 节展示的交互流程,依次输入USER、PASS、LIST、RETR 1,最后输入QUIT结束会话。

如果一切正常,你应该能在控制台看到刚才发送的邮件内容。

8. 总结与进一步扩展

通过本文的实践,你已经亲手实现了一个简化版的邮件服务器和客户端,理解了 SMTP 和 POP3 协议的核心交互流程。这个项目虽然简单,却涵盖了邮件系统最本质的机制。

如果你想继续深入,可以考虑以下扩展方向:

  • 增加 IMAP 协议支持,实现邮件的在线管理和文件夹操作。
  • 加入 TLS/SSL 加密,保障邮件传输安全。
  • 实现邮件附件(MIME 多部分)的解析和发送。
  • 增加用户认证和访问控制,防止未授权访问。
  • 将邮件存储改为数据库(如 SQLite),便于检索和管理。

邮件系统是一个庞大而精妙的工程,希望本文能成为你探索网络协议和系统编程的起点。

9. 搭建实用的邮件服务器确实不简单

需要安装 sendmail、dovocot等软件,需要申请互联网IP,需要申请DNS域名,需要申请证书
DNS申请原理,参考 https://blog.csdn.net/ggaofengg/article/details/164187662
免费证书也是可以申请的,但前提是需要有dns域名。
自己签发证书,可以没有域名,但其它邮件服务器不认,你的邮件会被当成垃圾邮件。

搭建邮件服务器确实曾以“配置复杂、坑多难调”著称,尤其是传统的 Sendmail 或 Postfix 组合,需要手动处理 DNS 记录、SSL 证书、反垃圾邮件策略等,稍有不慎就会导致邮件被拒收或进入垃圾箱。

不过,随着技术演进,现在已有多种‌极简方案‌能大幅降低门槛,甚至实现“5分钟部署”。以下是针对不同场景的简化搭建思路:

🚀 方案一:极致轻量化个人邮箱(推荐个人/小团队)
如果你只是想要一个专属域名的邮箱,且服务器配置较低,‌PMail‌ 是目前最简化的选择之一。

核心优势‌:
极简部署‌:基于 Go 语言开发,二进制文件仅 15MB,内存占用不到 10MB,1核512M内存的 VPS 即可流畅运行。
一键启动‌:支持 Docker 或二进制直接运行,无需复杂的依赖安装。
自动 SSL‌:内置 ACME 协议,自动申请和更新 Let’s Encrypt 证书,无需手动配置 HTTPS/SMTPS。
Web 向导配置‌:提供图形化初始化界面,引导完成域名、数据库和管理员账户设置。
部署示例‌:
docker run -p 25:25 -p 80:80 -p 443:443 -p 587:587 -p 993:993 -p 995:995
-v $(pwd)/config:/work/config ghcr.io/jinnrry/pmail:latest
启动后访问 Web 界面即可完成剩余配置,支持 DKIM/SPF 自动检测,确保高送达率。

我试了一下

sudo k3s ctr run --rm --net-host
–env setup_port=8008
–env http_proxy=http://127.0.0.1:1
–env https_proxy=http://127.0.0.1:1
–env no_proxy=127.0.0.1,localhost
–mount type=bind,src=/opt/pmail-config,dst=/work/config,options=rbind:rw
ghcr.io/jinnrry/pmail:latest pmail-server

用这种方式启动的容器,不能用kubectl命令,也不能用crictl命令查询和删除,crictl命令和docker命令比较接近,但它只能看到kubectl启动的pod中的容器
只能用最原始的ctr命令删除 sudo k3s ctr tasks kill pmail-server ; sleep 3; sudo k3s ctr c rm pmail-server

要点:
–net-host:host 网络直通端口
–env setup_port=8008:bootstrap 界面用 8008 端口
http_proxy=127.0.0.1:1:让 ip-api.com 那个无超时请求立刻连接失败,跳过卡死
挂载 /work/config(配置和 sqlite 数据库 pmail.db 都在这里,持久化)
Web 管理界面:浏览器打开 http://10.137.239.107:8008/

注意:这台机器上 8008 是我们自己起的容器。如果是正式部署,建议用 Kubernetes Deployment + Service(NodePort) 的方式,避免用--net-host直接占用宿主机端口。

二、初始化(第一次运行)
我通过 API 完整走完了 setup 向导,等价于你在浏览器里点一遍:

步骤 配置
数据库 sqlite → ./config/pmail.db
管理员 admin / Admin12345
域名 test.local
SSL 自签名证书(/opt/pmail-config/ssl/)+ 生成的 DKIM 密钥
配置文件在 /opt/pmail-config/config.json,其中 httpPort:8008(避开 traefik)、httpsEnabled:2(只用 HTTP)。

三、测试结果(全部通过)
当前运行的服务端口:8008(Web)、25(SMTP)、465(SMTP+SSL)、587(SMTP+STARTTLS)、110(POP3)、995(POP3+SSL)、993(IMAP+SSL)

测试项 结果
Web 界面 ✅ 200,正常返回前端
API ping ✅ pong
登录(admin/test2) ✅ 返回账户+域名信息
SMTP 发信 → IMAP 收信 ✅ 外部域名发件投递到 INBOX,主题/收发件人正确
SMTP SSL 465 认证登录 ✅
SMTP STARTTLS 587 ✅
POP3 110 ✅ 能列出邮件
Web 收件箱 API ✅ 显示邮件列表
四、一个重要的使用说明(测试时踩到的)
PMail 有反伪造检测:如果发件域名是本地配置的域名(如 test.local)且 SPF 校验失败,邮件会被标记为"伪造"投到"已删除"文件夹。我最初用 admin@test.local → test2@test.local 测试,邮件就进了 Deleted 而不是收件箱——这是正常的邮件安全行为,不是 bug。

本地测试:用非本地域名发件(如 someone@example.com → test2@test.local),验证通过 ✅
真实使用:配置好域名的 SPF/DKIM DNS 记录后,本地用户互发不会被误判
五、邮件客户端配置(真实使用)
IMAP:imap.test.local,端口 993 (SSL)
SMTP:smtp.test.local,端口 465 (SSL) 或 587 (STARTTLS)
POP3:pop.test.local,端口 110/995
当前容器还在运行,你可以直接打开 http://10.137.239.107:8008/ 用 admin/Admin12345 登录体验。需要我帮你把它整理成一个正式的 K8s Deployment YAML,或者把 test.local 换成你真实的域名重新配置吗?

echo “=== create test2 ===”

curl --noproxy ‘*’ -s -b /tmp/pmail.cookies -X POST http://127.0.0.1:8008/api/user/create -H ‘Content-Type: application/json’ -d ‘{“username”:“test2”,“password”:“Test2123”,“account”:“test2”}’

echo; echo “=== user list ===”;

curl --noproxy ‘*’ -s -b /tmp/pmail.cookies -X POST http://127.0.0.1:8008/api/user/list -H ‘Content-Type: application/json’ -d ‘{“current_page”:1,“page_size”:10}’|jq

python3 << ‘EOF’
import sqlite3
con = sqlite3.connect(“/opt/pmail-config/pmail.db”)
cur = con.cursor()
cur.execute(“SELECT name FROM sqlite_master WHERE type=‘table’”)
print(“[tables]”, [r[0] for r in cur.fetchall()])
for t in [“email”,“email_queue”,“mail_queue”,“send_queue”,“email_send”,“user_mail”]:
try:
cur.execute(f"SELECT count(*) FROM {t}“)
print(f”[{t}] count:", cur.fetchone()[0])
except Exception as e:
pass
con.close()
EOF

可以直接查询数据库,不需要密码。这就是sqllite的安全模型,依赖操作系统对这个db文件的权限
sqlite3 /opt/pmail-config/pmail.db “SELECT id,account,name,is_admin FROM user;”

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 22:50:47

金融人必备:收藏!33岁零代码转行AI,3个月上岸中信证券35K经验分享

本文分享了一位拥有6年金融经验的金融人的AI转型成功案例。文章核心内容为&#xff1a;AI时代&#xff0c;金融经验是宝贵资产&#xff0c;转型AI无需从零开始&#xff0c;关键在于将金融业务与AI应用能力相结合。通过明确AI在金融场景中的应用方向&#xff0c;并围绕金融业务痛…

作者头像 李华
网站建设 2026/10/8 22:49:42

[开源]基于STM32单片机WiFi语音识别垃圾分类系统 Onenet语音分类垃圾桶 云平台智能垃圾分类系统 app垃圾桶火灾预防设计HK-024

1、前言 本设计以STM32F103C8T6单片机为主控芯片&#xff0c;WIFI模块ESP8266-01S作为通信模块连接onenet云平台&#xff0c;通过通过MQ-2烟雾传感器检测烟雾浓度是否正常&#xff0c;预防垃圾桶火灾发生。OLED屏幕显示烟雾浓度、当前厨余垃圾&#xff0c;其他垃圾&#xff0c;…

作者头像 李华
网站建设 2026/10/8 22:45:44

Codex 智能体:AI 编程从写代码到改仓库,工作流重构实战

DevDay 这种发布会刷屏的时候&#xff0c;我一般不太急着追。二十多项更新看着热闹&#xff0c;但真正值得我放下手里的活去试的&#xff0c;通常只有一两条。这次也一样——模型又变强了、API 又降价了、平台又多了几个新工具&#xff0c;可这些东西说到底都是在“原来的玩法”…

作者头像 李华
网站建设 2026/10/8 22:43:19

制造业企业宣传片全链路落地实战指南

在制造业的资本市场上&#xff0c;一场无声的较量正在上演。当两家技术实力相当的精密设备厂商同台路演&#xff0c;决定投资人最终倾向的&#xff0c;往往不再是枯燥的财报数据&#xff0c;而是谁能用更直观的视觉语言讲清楚“技术壁垒”在哪里。很多创始人都有过这样的经历&a…

作者头像 李华
网站建设 2026/10/8 22:41:04

基于Flask的毕业生就业管理系统设计与实践

1. 毕业生就业管理系统到底要做什么1.1 项目本质&#xff1a;帮谁解决什么问题先把这个标题拆开看。基于 Flask 框架、用 Python 写、做的是毕业生就业管理系统&#xff0c;这三件事放在一起&#xff0c;实际上指向的是高校就业办、二级学院辅导员和应届毕业生这三类角色的日常…

作者头像 李华