W55MH32发送邮件给他人
更新于 2026年9月30日
本篇总结
在 W55MH32 上实现 SMTP 协议,通过 126 邮箱服务器发送电子邮件到指定收件人。
硬件平台:本例程基于 W55MH32L-EVB 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB
本示例演示如何在 W55MH32 上实现 SMTP 协议,通过实战例程讲解如何在 W55MH32 上使用 SMTP 协议给他人发送电子邮件。该例程用到的其他网络协议(例如 DHCP、DNS),请参考相关章节。有关 W55MH32 的初始化过程,请参考 Network install 章节,这里不再赘述。
前置工具准备
开始本例程前,请确认以下工具已安装:
- WIZ UartTool V1.0:串口调试工具:点击下载
SMTP 协议简介
SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是一种用于电子邮件传输的通信协议。它是互联网标准协议之一,专门设计用于电子邮件的发送和路由。SMTP 定义了邮件如何从发件人发送到收件人的电子邮件服务器,并规范了服务器之间的邮件中继操作。
SMTP 协议特点:
- 面向文本:SMTP 使用纯文本命令和响应(如 HELO、MAIL FROM、RCPT TO 等)进行通信,邮件内容通常以 ASCII 码表示
- 请求-响应模式:基于请求-响应模型,客户端发送请求,服务器返回响应
- 基于 TCP:依赖 TCP 协议提供可靠数据传输,通过 TCP 建立连接并发送邮件
- 广泛兼容性:国际标准邮件传输协议,广泛应用于邮件系统,确保不同系统间高效互通
- 高效性与可靠性:协议简单易用,支持错误处理和重试机制,邮件无法发送时可暂存队列并重试
- 可扩展性:通过扩展命令和响应码,支持更多邮件传输特性和功能
- 安全性:本身不处理加密,但可结合 SSL/TLS(SMTPS)提供加密通道
- 异步传输:支持异步传输,邮件发送和接收可在不同时间进行,提升效率并支持批量处理
- 灵活性:可配置邮件路由、优先级、大小限制等,满足不同需求
SMTP 应用场景
① 物联网设备远程监控:收集传感器数据,发送定期报告或警报邮件;② 环境监测系统:将温湿度、CO2 浓度等监测数据发送到指定邮箱;③ 设备状态报告与日志记录:定时触发自动发送设备状态或运行日志;④ 远程故障报警与支持:结合传感器实现自动报警;⑤ 工业自动化和远程监控:发送设备状态、报警等信息到指定邮箱;⑥ 远程控制反馈:接收邮件解析命令并执行,通过 SMTP 反馈结果。
SMTP 发送邮件流程
- 使用 TCP 协议连接 SMTP 服务器
- 发送握手消息
- 发送用户认证消息
- 设置邮件发送地址
- 传输邮件内容
- 完成邮件发送
硬件连接与拓扑
请注意:
因为本示例需要访问互联网,请确保 W55MH32 的配置能够访问互联网。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口,路由器需连接互联网
│ W55MH32 │──网线──│ 路由器/网关 │──互联网──│ SMTP 服务器 │
│ (SMTP │ │ (DHCP/DNS) │ │ smtp.126.com │
│ Client) │ │ 192.168.1.1 │ │ TCP :25 │
└───────────┘ └─────────────────┘ └──────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
工程结构
工程位于 SDK 内 1.SDK\ModuleDemo\WZTOE\SMTP\,核心文件如下:
SMTP/
├── Project/
│ └── Project.uvprojx ← Keil 工程文件
└── USER/
├── main.c ← 初始化 + DNS 解析 + SMTP 主循环
├── smtp.c/h ← SMTP 协议实现(send_mail / do_smtp / base64encode)
├── do_dns/ ← DNS 域名解析
└── uart/
└── bsp_uart.c/h ← 调试串口 + printf 重定向
步骤1:SMTP 发送内容初始化
文件:USER/smtp.c — 定义 SMTP 通信过程中用到的命令字符串、认证信息、收件人/发件人地址等。
char hello[50] = "HELO localhost"; // Identity command
char hello_reply[] = "250 OK"; // Id successfully responded
char AUTH[50] = "AUTH LOGIN"; // Authentication request
char AUTH_reply[] = "334 dXNlcm5hbWU6"; // The authentication request was successfully sent
char name_126[100] = "[email protected]"; // 126 Login email address
char base64name_126[200]; // 126 base64 encoding of the login mailbox name
char name_reply[] = "334 UGFzc3dvcmQ6"; // The login name was sent successfully
char password_126[50] = "ZPURADLGRUPQLVBK"; // 126 Email login password
char base64password_126[100]; // 126 base64 Password
char password_reply[] = "235 Authentication successful"; // Login successful response
char from[] = "[email protected]"; // Sender email
char from_reply[] = "250 Mail OK";
char to[] = "[email protected]"; // Recipient email address
char to_reply[] = "250 Mail OK";
char data_init[10] = "data"; // Request data transfer
char data_reply[] = "354"; // The request was successfully responded to HEAD
char Cc[] = ""; // Cc to email
char subject[] = "Hello!WIZnet!"; // subject
char content[] = "Hello!WIZnet!"; // text part
char mime_reply[] = "250 Mail OK queued as"; // The email was sent successfully
char mailfrom[50] = "MAIL FROM:<>";
char rcptto[50] = "rcpt to:<>";
char mime[200] = "From:\r\n";
char mime1[50] = "To:\r\n";
char mime2[50] = "Cc:\r\n";
char mime3[50] = "Subject:\r\n";
char mime4[50] = "MIME-Version:1.0\r\nContent-Type:text/plain\r\n\r\n";
char mime5[50] = "\r\n.\r\n";需要修改的关键信息:发送方邮箱(name_126 / from)、发送方授权码(password_126,注意 126 邮箱使用的是授权码而非登录密码)、收件人邮箱(to)、邮件主题与内容(subject / content)。
使用前的三点注意事项
- 修改邮箱账号及收发件人信息
- 邮箱必须开启 SMTP 功能(126 邮箱需在设置中开启 SMTP 服务并获取授权码)
- 确认邮箱所用的 SMTP 服务器地址和端口号(126 邮箱为 smtp.126.com:25)
步骤2:发送邮件内容初始化
文件:USER/smtp.c | 函数:mailmessage() — 对发送内容进行拼接处理,包括:Hello / AUTH 命令加 \r\n、用户名和密码 Base64 编码、插入发件人/收件人/抄送/主题到对应命令字符串、拼接 MIME 报文头。
void mailmessage(void)
{
uint16_t len_from = strlen(from);
uint16_t len_to = strlen(to);
uint16_t len_Cc = strlen(Cc);
uint16_t len_sub = strlen(subject);
strcat(hello, "\r\n");
strcat(AUTH, "\r\n");
base64encode(name_126, base64name_126);
base64encode(password_126, base64password_126);
strcat(base64name_126, "\r\n");
strcat(base64password_126, "\r\n");
str_insert(mailfrom, from, 11);
strcat(mailfrom, "\r\n");
str_insert(rcptto, to, 9);
strcat(rcptto, "\r\n");
strcat(data_init, "\r\n");
str_insert(mime, from, 5);
str_insert(mime1, to, 3);
str_insert(mime2, Cc, 3);
str_insert(mime3, subject, 8);
str_insert(mime5, content, 0);
strcat(mime, mime1);
strcat(mime, mime2);
strcat(mime, mime3);
strcat(mime, mime4);
strcat(mime, mime5);
}
Base64 编码:SMTP 认证过程中,用户名和密码需要进行 Base64 编码传输。base64encode() 函数实现了标准的 Base64 编码算法。
字符串插入:str_insert(s1, s2, pos) 把字符串 s2 插入到 s1 的 pos 位置处,例如把邮箱地址插入到 "MAIL FROM:<>" 的尖括号中间。
步骤3:DNS 解析 SMTP 服务器地址
文件:USER/main.c — 使用 do_dns() 将 SMTP 服务器域名 smtp.126.com 解析为 IP 地址。
uint8_t smtp_server_name[] = "smtp.126.com";
uint8_t smtp_server_ip[4] = {0};
mailmessage(); // Mail command information processing
if (do_dns(ethernet_buf, smtp_server_name, smtp_server_ip))
{
while (1)
{
}
}
注意:mailmessage() 必须在 DNS 解析之前调用,因为它只做字符串拼接,不依赖网络。do_dns() 成功后 smtp_server_ip 中即为 SMTP 服务器 IP。
步骤4:SMTP 发送邮件操作
文件:USER/smtp.c | 函数:do_smtp() — 一个 TCP Client 状态机:SOCK_CLOSED 时打开 Socket,SOCK_INIT 时 connect() 连接 SMTP 服务器 25 端口,SOCK_ESTABLISHED 时接收服务器响应并调用 send_mail() 处理状态机。
void do_smtp(uint8_t sn, uint8_t *buf, uint8_t *smtp_server_ip)
{
volatile uint8_t ret;
uint32_t len = 0;
uint16_t anyport = 5000;
uint8_t Smtp_PORT = 25;
memset(buf, 0, ETHERNET_MAX_BUF_SIZE);
switch (getSn_SR(sn))
{
case SOCK_INIT:
ret = connect(sn, smtp_server_ip, Smtp_PORT);
break;
case SOCK_ESTABLISHED:
if (getSn_IR(sn) & Sn_IR_CON)
{
setSn_IR(sn, Sn_IR_CON);
}
while (!Mail_Send_OK)
{
len = getSn_RX_RSR(sn);
if (len > 0)
{
memset(buf, 0, ETHERNET_MAX_BUF_SIZE);
len = recv(sn, (uint8_t *)buf, len);
send_mail(sn, buf, smtp_server_ip);
}
}
disconnect(sn);
break;
case SOCK_CLOSE_WAIT:
if ((len = getSn_RX_RSR(sn)) > 0)
{
while (!Mail_Send_OK)
{
len = recv(sn, (uint8_t *)buf, len);
send_mail(sn, buf, smtp_server_ip);
}
}
disconnect(sn);
break;
case SOCK_CLOSED:
socket(sn, Sn_MR_TCP, anyport++, 0x00);
break;
default:
break;
}
if (Mail_Send_OK)
{
while (1)
{
}
}
}
send_mail() 函数实现了完整的 SMTP 状态机,通过 SMTP_STATE 变量在 8 个状态间切换:
void send_mail(uint8_t sn, uint8_t *buf, uint8_t *smtp_server_ip)
{
volatile uint8_t ret;
switch (SMTP_STATE)
{
case waitfor220:
if (strstr((const char *)buf, "220") != NULL)
{
ret = send(sn, (uint8_t *)hello, strlen(hello));
SMTP_STATE = waitforHELO250;
}
else printf("Connected failed!\r\n");
break;
case waitforHELO250:
if (strstr((const char *)buf, hello_reply) != NULL && strstr((const char *)buf, "Mail") == NULL)
{
ret = send(sn, (uint8_t *)AUTH, strlen(AUTH));
SMTP_STATE = waitforAUTH334;
}
else printf("smtp handshake failed!\r\n");
break;
case waitforAUTH334:
if (strstr((const char *)buf, AUTH_reply) != NULL)
{
ret = send(sn, (uint8_t *)base64name_126, strlen(base64name_126));
SMTP_STATE = waitforuser334;
}
else printf("AUTH authentication request failed!\r\n");
break;
case waitforuser334:
if (strstr((const char *)buf, name_reply) != NULL)
{
ret = send(sn, (uint8_t *)base64password_126, strlen(base64password_126));
SMTP_STATE = waitforpassword235;
}
else printf("username send failed!\r\n");
break;
case waitforpassword235:
if (strstr((const char *)buf, password_reply) != NULL)
{
ret = send(sn, (uint8_t *)mailfrom, strlen(mailfrom));
SMTP_STATE = waitforsend250;
}
else printf("password error!\r\n");
break;
case waitforsend250:
if (strstr((const char *)buf, from_reply) != NULL && strstr((const char *)buf, "queued as") == NULL)
{
ret = send(sn, (uint8_t *)rcptto, strlen(rcptto));
SMTP_STATE = waitforrcpt250;
}
else printf("Send email failed to set up!\r\n");
break;
case waitforrcpt250:
if (strstr((const char *)buf, to_reply) != NULL)
{
ret = send(sn, (uint8_t *)data_init, strlen(data_init));
SMTP_STATE = waitfordate354;
}
else printf("Failed to set the receiving mailbox!\r\n");
break;
case waitfordate354:
if (strstr((const char *)buf, data_reply) != NULL)
{
ret = send(sn, (uint8_t *)mime, strlen(mime));
SMTP_STATE = waitformime250;
}
else printf("Failed to send content setup\r\n");
break;
case waitformime250:
if (strstr((const char *)buf, mime_reply) != NULL)
{
Mail_Send_OK = 1;
printf("mail send OK\r\n");
}
break;
default:
break;
}
}
SMTP 状态机流程:① 握手:发送 HELO 命令,等待 250 OK;② 用户认证:发送 AUTH LOGIN → 334 请求用户名 → 发送 Base64 用户名 → 334 请求密码 → 发送 Base64 密码 → 235 认证成功;③ 设置地址:MAIL FROM → 250 → RCPT TO → 250;④ 传输内容:DATA → 354 → 发送 MIME 报文 → 250 排队成功;⑤ 设置 Mail_Send_OK = 1 并打印 mail send OK。
运行验证
打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1)。烧录例程运行后,首先进行 PHY 链路检测和 DHCP 获取网络地址,然后 DNS 解析 SMTP 服务器域名,最后连接 SMTP 服务器并发送邮件。预期串口输出如下:
W55MH32 SMTP example
PHY link
The current Mbtis speed : 100Mbps
The current Duplex Mode : Full-Duplex
DHCP running
DHCP success!
====================================================================================================
W55MH32 network configuration : DHCP
MAC : 00:08:DC:00:0E:9A
IP : 192.168.1.6
Subnet Mask : 255.255.255.0
Gateway : 192.168.1.1
DNS : 192.168.1.1
====================================================================================================
> Translated smtp.126.com to 111.124.203.46
mail send OK
串口打印 mail send OK 表示邮件发送成功。可在收件人邮箱中查收到的邮件,邮件主题为 Hello!WIZnet!。


常见问题
Q: 邮件发送失败怎么办
排查步骤:① 确认开发板能访问互联网;② 确认 DNS 解析成功(串口打印 > Translated smtp.126.com to ...);③ 确认邮箱已开启 SMTP 服务并获取授权码;④ 确认发件人、收件人邮箱地址填写正确;⑤ 检查 25 端口是否被防火墙拦截;⑥ 查看串口打印的错误信息定位状态机失败的位置。
Q: 为什么使用 126 邮箱的授权码而不是登录密码
126 邮箱(以及大部分公共邮箱)出于安全考虑,在第三方客户端登录时要求使用专门的"授权码"而不是登录密码。授权码在邮箱设置中开启 SMTP 服务后生成,作为第三方客户端的登录密码使用。
Q: 为什么用户名和密码需要 Base64 编码
SMTP 认证过程中(AUTH LOGIN),用户名和密码按照 Base64 编码传输。这是 SMTP 协议规范的要求,Base64 编码后的字符都是可打印 ASCII 字符,便于在文本协议中传输。base64encode() 函数实现了标准 Base64 编码。
Q: SMTP 默认端口是多少,可以修改吗
SMTP 默认使用 TCP 25 端口。若使用 SSL/TLS 加密(SMTPS),则使用 465 端口。本例程使用明文 25 端口。do_smtp() 函数中的 Smtp_PORT 变量可修改为其他端口。
Q: 如何发送带附件的邮件
本例程只发送纯文本邮件,Content-Type 为 text/plain。要发送附件需修改 MIME 报文头为 multipart/mixed,并按照 MIME 规范对附件进行 Base64 编码后拼接到邮件体中,工作量和内存占用都会显著增大。
总结
本文讲解了如何在 W55MH32 芯片上实现 SMTP 协议:
- SMTP 协议:基于 TCP 25 端口的简单邮件传输协议,采用请求-响应模式,报文纯文本
- 发送流程:TCP 连接 → HELO 握手 → AUTH LOGIN 认证 → MAIL FROM / RCPT TO 设置地址 → DATA 传输内容 → 完成
- 关键 API:
mailmessage()内容拼接、base64encode()认证编码、do_smtp()TCP 状态机、send_mail()SMTP 状态机 - 状态机模式:通过
SMTP_STATE变量在 8 个状态间切换,每收到一次服务器响应就推进到下一个状态 - 应用价值:设备远程监控、环境监测、故障报警、日志记录等场景中通过邮件通知管理人员