全部例程

W55MH32发送邮件给他人

W55MH32

更新于 2026年9月30日

本篇总结

在 W55MH32 上实现 SMTP 协议,通过 126 邮箱服务器发送电子邮件到指定收件人。

🔑 KEIL MDK、UART调试、硬件TOE引擎、SMTP、DNS、DHCP

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

本示例演示如何在 W55MH32 上实现 SMTP 协议,通过实战例程讲解如何在 W55MH32 上使用 SMTP 协议给他人发送电子邮件。该例程用到的其他网络协议(例如 DHCP、DNS),请参考相关章节。有关 W55MH32 的初始化过程,请参考 Network install 章节,这里不再赘述。

前置工具准备

开始本例程前,请确认以下工具已安装:

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 发送邮件流程

  1. 使用 TCP 协议连接 SMTP 服务器
  2. 发送握手消息
  3. 发送用户认证消息
  4. 设置邮件发送地址
  5. 传输邮件内容
  6. 完成邮件发送

硬件连接与拓扑

请注意:

因为本示例需要访问互联网,请确保 W55MH32 的配置能够访问互联网。

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口,路由器需连接互联网
┌───────────┐ ┌─────────────────┐ ┌──────────────────┐
│ W55MH32 │──网线──│ 路由器/网关 │──互联网──│ SMTP 服务器 │
│ (SMTP │ │ (DHCP/DNS) │ │ smtp.126.com │
│ Client) │ │ 192.168.1.1 │ │ TCP :25 │
└───────────┘ └─────────────────┘ └──────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
网络拓扑:W55MH32 通过路由器接入互联网,连接 126 邮箱 SMTP 服务器发送邮件

工程结构

工程位于 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!。

WIZ UartTool 串口输出 — SMTP 邮件发送成功
收件箱 — 接收到的 SMTP 邮件(主题 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 个状态间切换,每收到一次服务器响应就推进到下一个状态
  • 应用价值:设备远程监控、环境监测、故障报警、日志记录等场景中通过邮件通知管理人员

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家