| Crates.io | smtp_recv |
| lib.rs | smtp_recv |
| version | 0.1.64 |
| created_at | 2025-11-28 17:36:52.918163+00 |
| updated_at | 2026-01-16 19:35:35.897726+00 |
| description | Dual-port SMTP server: Secure submission (465) & Inbound forwarding (25) / 双端口 SMTP 服务:安全提交 (465) 与入站转发 (25) |
| homepage | https://github.com/js0-site/rust/tree/main/smtp_recv |
| repository | https://github.com/js0-site/rust.git |
| max_upload_size | |
| id | 1955783 |
| size | 194,705 |
smtp_recv is a secure-by-default, high-performance SMTP server implementation in Rust, featuring a Dual-Port Strategy that simultaneously handles Inbound Email Forwarding and Authenticated Outbound Submission.
Designed for modern email architectures, it enforces strict security standards, leverages tokio for asynchronous I/O, and integrates seamlessly with your existing infrastructure via simple Traits.
smtp_recv employs a unique dual-port approach to handle distinct email flows securely and efficiently:
STARTTLS for opportunistic encryption. If the client provides a valid SNI matching your certificate, the connection upgrades to TLS. In plaintext mode, it defaults to localhost.Forward trait (e.g., check user existence, query routing rules).Mailer trait (e.g., for storage or relay). If invalid, reject with 550 No such user.contact@yourdomain.com and forwarding it to your backend or personal inbox.Auth trait.Mailer trait.graph TD
Internet["Internet Mail Servers<br/>(Gmail/Outlook)"] -->|Deliver MX| P25
User["User Client<br/>(Outlook/iOS)"] -->|Submit Auth| P465
subgraph Service [smtp_recv Service]
P25["Port 25 (SMTP)<br/>Inbound Forwarding"]
P465["Port 465 (SMTPS)<br/>Secure Submission"]
Auth["Auth Logic<br/>(Auth Trait)"]
Forward["Forwarding Logic<br/>(Forward Trait)"]
Queue["Unified Processing<br/>(Mailer Trait)"]
end
P25 -->|STARTTLS| Forward
P465 -->|Implicit TLS| Auth
Forward --> Queue
Auth --> Queue
Queue -->|Process| Backend["Backend System<br/>(Message Queue/DB)"]
MAIL -> RCPT -> DATA).tokio runtime.Add smtp_recv to your Cargo.toml. You will typically implement the required traits to bridge the server with your application's logic.
You need to provide implementations for three key components:
Auth (Port 465): Verifies username/password.Forward (Port 25): Validates recipients and determines forwarding targets.Mailer: The final destination for all valid emails (e.g., save to disk, push to Redis).CertByHost: Provides SSL certificates based on SNI.See tests/main.rs for a complete runnable example. Here is a simplified version:
use smtp_recv::run;
use tokio_util::sync::CancellationToken;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cancel = CancellationToken::new();
// The 'run' function automatically binds to both ports 25 and 465
// and starts handling traffic.
run(
my_forward_impl, // impl mail_forward::Forward
my_auth_impl, // impl auth_trait::Auth
my_mailer_impl, // impl smtp_recv::Mailer
my_cert_impl, // impl ssl_trait::CertByHost
cancel
).await?;
Ok(())
}
The system is designed around a unified Session model. Whether a connection comes from Port 25 or 465, it is eventually handled by a shared command processing loop (session::run_loop), ensuring consistent behavior.
STARTTLS in the EHLO response.MAIL FROM: Validates the sender. on Port 465, ensures the user is authenticated.RCPT TO: On Port 25, calls forward.forward_set() to validate and rewrite recipients.DATA command and content, the full email object is constructed and passed to mailer.send().tokio (Async I/O)rustls, tokio-rustls (Modern, secure TLS implementation)anyhow, thiserrorsrc/
├── lib.rs # Library entry point, exports run/bind
├── bind.rs # Generic socket binding logic
├── accept/ # Connection acceptance and TLS handling
│ ├── mod.rs
│ ├── forward.rs # Port 25 specific logic
│ └── send.rs # Port 465 specific logic
├── error.rs # Error type definitions
├── mailer.rs # Mailer trait definition
├── session.rs # Core SMTP session state machine
├── forward/ # Forwarding session specific logic
└── send/ # Sending session specific logic
smtp_recv::runpub async fn run(
forward: impl mail_forward::Forward,
auth: impl auth_trait::Auth,
mailer: impl Mailer,
ssl: impl CertByHost,
cancel_token: CancellationToken,
) -> Result<()>
Starts the server. This functions returns a Future that resolves only when the server is shut down via the cancel_token.
smtp_recv::Mailerpub trait Mailer: Clone + Send + Sync + 'static {
fn send(&self, mail: UserMail) -> impl Future<Output = Result<()>> + Send;
}
Implementation logic for processing received emails.
The Tale of Port 465
In the late 1990s, as email security became a concern, Port 465 was originally designated for "SMTPS" - SMTP over SSL. The idea was simple: encrypted from the very first byte. However, IANA and IETF standards bodies favored a different approach called STARTTLS (which reuses Port 25 or 587 and upgrades the connection later), leading to Port 465 being formally "deprecated" for a time.
Despite this, the real world often disagrees with standards committees. STARTTLS proved vulnerable to "stripping attacks" where a malicious intermediary could remove the encryption capability from the handshake, forcing a fallback to plaintext.
Because of this specific vulnerability, major providers like Gmail and Outlook continued to support Port 465. Its "implicit TLS" model offers no opportunity for downgrade attacks—if you can't speak TLS, you can't talk at all. Today, Port 465 has seen a massive resurgence and is now the recommended best practice for secure email submission ("JMAP" and modern RFCs have since acknowledged its validity), proving that sometimes, the simple, secure-by-default option wins in the long run.
---
## About
This project is an open-source component of [js0.site ⋅ Refactoring the Internet Plan](https://js0.site).
We are redefining the development paradigm of the Internet in a componentized way. Welcome to follow us:
* [Google Group](https://groups.google.com/g/js0-site)
* [js0site.bsky.social](https://bsky.app/profile/js0site.bsky.social)
---
<a id="zh"></a>
# smtp_recv: 双端口安全 SMTP 服务器
**smtp_recv** 是一个基于 Rust 实现的安全优先、高性能 SMTP 服务器,采用**双端口策略**,同时支持**端口 25 的入站邮件转发**和**端口 465 的出站邮件安全提交**。
即使是在高并发环境下,也能保持极低的资源占用。通过强制的安全默认设置(如 Implicit TLS 和 SNI),它为现代邮件架构提供了坚实的基础设施。
## 目录
- [核心架构](#核心架构)
- [功能特性](#功能特性)
- [安装与集成](#安装与集成)
- [工作原理](#工作原理)
- [技术堆栈](#技术堆栈)
- [目录结构](#目录结构)
- [API 参考](#api-参考)
- [历史背景](#历史背景)
## 核心架构
**smtp_recv** 将不同的邮件流量隔离在两个专用的端口上,各自执行不同的安全和业务逻辑:
### 1. 25 端口: 入站转发 (MX)
* **角色**: 作为对外的 MX 服务器,接收处理来自互联网的邮件投递。
* **安全**: 支持 `STARTTLS` 扩展。如果客户端握手时的 SNI 与服务器证书匹配,连接将自动升级为加密通道;否则(如纯文本连接)默认使用 `localhost` 作为主机名,兼容旧式服务器。
* **流程**:
1. 建立 TCP 连接。
2. 协商 STARTTLS(如支持)。
3. 在 `RCPT TO` 阶段调用 `Forward` trait 验证收件人(查询用户是否存在、路由规则)。
4. 验证通过后,接收完整邮件并通过 `Mailer` trait 将其交付(例如转发到后端服务或存储)。若用户不存在,直接返回 `550 No such user`。
* **场景**: 接收发往 `user@yourdomain.com` 的邮件,并将其转发到 Gmail 或内部处理系统。
### 2. 465 端口: 出站提交 (Submission)
* **角色**: 专为 authenticated user(受信任的客户端,如 Outlook、Thunderbird、手机邮件App)设计的安全网关。
* **安全**: **强制隐式 TLS (Implicit TLS)**。连接建立的瞬间即开始 TLS 握手。任何非加密数据包都会被立即拒绝,从根本上杜绝了中间人降级攻击(Downgrade Attack)。
* **流程**:
1. 建立 TCP 连接。
2. **立即**完成 TLS 握手(必须提供 SNI)。
3. 强制 SMTP 认证 (AUTH PLAIN/LOGIN),通过 `Auth` trait 验证凭据。
4. 接收邮件并放入发送队列(通过 `Mailer` trait)。
* **场景**: 为您的用户提供安全可靠的邮件发送服务。
### 架构示意图
```mermaid
graph TD
Internet["互联网邮件服务器<br/>(Gmail/Outlook)"] -->|投递 MX| P25
User["用户客户端<br/>(Outlook/iOS)"] -->|提交 Auth| P465
subgraph Service [smtp_recv 服务]
P25["端口 25 (SMTP)<br/>入站转发"]
P465["端口 465 (SMTPS)<br/>安全提交"]
Auth["认证逻辑<br/>(Auth Trait)"]
Forward["转发逻辑<br/>(Forward Trait)"]
Queue["统一处理<br/>(Mailer Trait)"]
end
P25 -->|STARTTLS| Forward
P465 -->|Implicit TLS| Auth
Forward --> Queue
Auth --> Queue
Queue -->|处理| Backend["后端系统<br/>(消息队列/数据库)"]
MAIL -> RCPT -> DATA),有效防御协议层面的攻击。tokio 运行时,轻松应对高并发连接。在 Cargo.toml 中添加依赖后,您只需实现几个核心 traits 即可启动服务器。
您需要定义三个核心组件的行为:
Auth (465 端口): 负责验证用户名和密码。Forward (25 端口): 负责验证收件人是否有效,并返回实际的转发目标地址。Mailer: 整个系统的终点。无论是转发进来的邮件,还是用户发出的邮件,最终都汇聚于此进行处理(如写入 Redis 队列、存储到 S3 等)。CertByHost: 根据 SNI 提供证书。完整代码请参考 tests/main.rs。最简启动示例如下:
use smtp_recv::run;
use tokio_util::sync::CancellationToken;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cancel = CancellationToken::new();
// run 函数会自动同时监听 25 和 465 端口
// 并开始处理请求
run(
my_forward_impl, // impl mail_forward::Forward
my_auth_impl, // impl auth_trait::Auth
my_mailer_impl, // impl smtp_recv::Mailer
my_cert_impl, // impl ssl_trait::CertByHost
cancel
).await?;
Ok(())
}
项目内部采用了统一的 Session 抽象。无论连接来自端口 25 还是 465,最终都由一个共享的命令处理循环 (session::run_loop) 驱动,确保了一致的行为和稳定性。
STARTTLS。MAIL FROM: 验证发件人。465 端口在此处检查是否已认证。RCPT TO: 25 端口在此处调用 forward.forward_set(),对收件人进行清洗和重写。DATA 指令及内容后,构建完整的邮件对象,交给 mailer.send() 处理。tokio (全异步非阻塞)rustls, tokio-rustls (内存安全、高性能的现代 TLS 实现)anyhow, thiserrorsrc/
├── lib.rs # 库入口,导出 run/bind
├── bind.rs # 通用的 TCP 绑定与监听逻辑
├── accept/ # 连接接收器
│ ├── mod.rs
│ ├── forward.rs # 25 端口专用逻辑
│ └── send.rs # 465 端口专用逻辑
├── error.rs # 错误类型定义
├── mailer.rs # Mailer trait 定义
├── session.rs # 核心 SMTP 会话状态机
├── forward/ # 转发模式会话实现
└── send/ # 发送模式会话实现
smtp_recv::runpub async fn run(
forward: impl mail_forward::Forward,
auth: impl auth_trait::Auth,
mailer: impl Mailer,
ssl: impl CertByHost,
cancel_token: CancellationToken,
) -> Result<()>
启动服务器。该函数返回一个 Future,仅在通过 cancel_token 触发停机时才会结束。
smtp_recv::Mailerpub trait Mailer: Clone + Send + Sync + 'static {
fn send(&self, mail: UserMail) -> impl Future<Output = Result<()>> + Send;
}
实现邮件落地的具体业务逻辑。
465 端口的前世今生
上世纪 90 年代末,随着电子邮件安全日益受到重视,465 端口最初被分配给 "SMTPS" —— 即基于 SSL 的 SMTP。其理念非常简单直接:从连接建立的第一个字节开始就进行加密。然而,IANA 和 IETF 标准组织后来倾向于使用 STARTTLS 机制(复用 25 或 587 端口,通过命令升级连接),这导致 465 端口一度被正式标记为"已废弃"。
尽管标准如此,真实世界的需求却给出了不同的答案。STARTTLS 被证明存在"剥离攻击"(Stripping Attack)的风险:恶意的中间人可以拦截并移除 STARTTLS 通告,迫使客户端回退到明文传输,而用户往往毫无察觉。
正因为这种无法根除的中间人风险,Gmail、Outlook 等主流服务商始终坚持支持 465 端口。它的 "Implicit TLS" 模式不给降级攻击留任何机会——如果不加密,就无法通信。时至今日,465 端口经历了重大的复兴,已重新成为安全提交邮件的推荐实践(JMAP 等现代标准已承认其地位),这证明了有时"默认安全"的简单设计才是经得起时间考验的真理。
本项目为 js0.site ⋅ 重构互联网计划 的开源组件。
我们正在以组件化的方式重新定义互联网的开发范式,欢迎关注: