libcurl 是 c++ 发 smtp 邮件最稳方案,封装连接、加密、认证与命令序列;需注意 ssl 验证、邮箱格式、应用专用密码、crlf 换行及 mime 边界规范。

直接用 libcurl 发 SMTP 邮件最稳
纯 socket 自己拼 SMTP 协议容易卡在 AUTH、STARTTLS 或 535 错误上,调试成本远高于引入一个轻量库。C++ 没原生邮件支持,libcurl 是事实标准——它把连接、加密、认证、命令序列全封装好了,且 Windows/macOS/Linux 全平台可用。
常见错误现象:CURLOPT_SSL_VERIFYPEER 默认开启导致自签名证书失败;CURLOPT_MAIL_FROM 里用了中文或空格没 URL 编码;密码含特殊字符(如 @、/)没做 percent-encoding。
- 用
curl_easy_setopt(curl, CURLOPT_URL, "smtps://smtp.gmail.com:465"),别写成smtp://—— Gmail/Outlook 等主流服务强制要求 TLS - 必须设
CURLOPT_USERNAME和CURLOPT_PASSWORD,不能靠Authorization: Basic手动加 header - 发件人地址(
CURLOPT_MAIL_FROM)和收件人(CURLOPT_MAIL_RCPT)必须是合法邮箱格式,且与认证账号一致(Gmail 要求发件人 = 登录用户名) - 若用 Gmail,得开“应用专用密码”,不能用主密码;Outlook 要关“双重验证”或同样配应用密码
std::string 拼邮件正文时换行符必须是 "\r\n"
SMTP 协议规定所有命令和内容行结尾必须是 CRLF(\r\n),不是 \n。Linux/macOS 下用 \n 看似能发出去,但某些服务器(如 Exchange)会静默丢弃或返回 501 Syntax error in parameters or arguments。
使用场景:构建 MIME 多部分邮件(带附件或 HTML 正文)时,边界分隔符(boundary)前后、header 与 body 之间、各 part 之间,全部要用 \r\n。
立即学习“C++免费学习笔记(深入)”;
- 别依赖
std::endl—— 它在不同平台行为不一致,一律手写"\r\n" - 如果用
std::stringstream拼,记得每行末尾显式加"\r\n",比如:ss - HTML 正文里嵌的
\n不影响渲染,但邮件协议层仍需 CRLF 分隔 header 和 body
用 openssl 编译的 libcurl 才支持 SMTPS
很多系统自带的 libcurl(尤其 macOS 的 curl-config --version 显示 libcurl/7.64.1 这类老版本)默认只链接 secure_transport,不支持 STARTTLS 或 SMTPS 的完整握手流程,连上就断,报错类似:Failed to connect to smtp.gmail.com port 465: Connection refused 或 SSL connect error。
性能 / 兼容性影响:用 openssl 后体积略增(+2–3MB),但换来的是对现代 TLS 1.2/1.3、SNI、证书链验证的完整支持。
- macOS 上用 Homebrew 装:
brew install curl-openssl,然后编译时指定路径:-I/opt/homebrew/opt/curl-openssl/include -L/opt/homebrew/opt/curl-openssl/lib - Linux 上确认
curl --version输出含OpenSSL/1.1.1或更高,不含SecureTransport或GnuTLS - Windows 上建议用 vcpkg:
vcpkg install curl:x64-windows,它默认链接 OpenSSL
附件二进制数据必须 Base64 编码 + Content-Transfer-Encoding: base64
直接把 std::vector<uint8_t></uint8_t> 写进 MIME body 会破坏边界分隔符(boundary 可能恰好出现在原始二进制里),服务器解析失败,报错如:554 Transaction failed: Invalid mime part 或静默截断。
参数差异:文本附件(如 .txt)可选 quoted-printable,但图片、PDF、ZIP 等一律用 base64,无例外。
- 别自己写 Base64 —— 用
libcurl自带的Curl_base64_encode()(头文件curl/curl.h已声明),或 Boost/openssl 的成熟实现 - 编码后每行严格限长 76 字符,末尾加
\r\n,最后一行也要有 - MIME header 中必须包含:
Content-Transfer-Encoding: base64\r\nContent-Type: image/png; name="chart.png"\r\nContent-Disposition: attachment; filename="chart.png"\r\n
复杂点在于 multipart boundary 的生成和插入位置——它必须唯一、不可出现在任何附件内容中,且每段前后都要包一层 --{boundary}\r\n,最后以 --{boundary}--\r\n 结尾。这个细节漏掉一行,整封邮件就解析失败。










