C++使用libcurl发送SMTP邮件无报错但目标邮箱收不到邮件
libcurl SMTP发信失败排查与修复
针对你疑问的明确答复
- 你在main里定义的
const char *data = payload_text本身是合法的内存指针,但你没有把它传给CURLOPT_READDATA(你实际传的是全局的payload_text),且就算传入,因为没有配套的读取位置跟踪逻辑,也无法正常工作。 - 你实现的
read_function不符合libcurl的回调规范:- 回调的第四个用户数据参数应为
void*类型,你写的char*存在类型兼容风险 - 没有维护读取偏移量,每次被调用都从payload起始位置拷贝数据,永远不会返回0(libcurl约定返回0代表数据传输完成,即EOF),会导致libcurl无限重复读取内容
- 没有判断缓冲区最大可写入长度
size * nmemb,直接拷贝全量payload会造成内存越界写入
- 回调的第四个用户数据参数应为
其他导致发信失败的致命代码错误
CURLOPT_MAIL_FROM参数格式错误:该参数要求传入包裹在尖括号里的完整发件邮箱地址,例如<youraccount@gmail.com>,你传入的普通字符串"my mail"会导致SMTP的MAIL FROM命令执行失败CURLOPT_MAIL_RCPT参数类型错误:该参数要求传入curl_slist*类型的收件人链表,不能直接传入字符串,你传裸值会导致libcurl无法正确生成SMTP的RCPT TO命令,服务端识别不到收件人- 邮件payload里的
To、From头填的是占位符mymail/mailto,不是真实邮箱地址,就算请求发出去也会被邮件服务商直接拦截丢弃 - 资源释放逻辑错误:仅在请求失败时调用
curl_easy_cleanup清理句柄,请求成功时会造成资源泄漏;如果curl初始化失败,直接调用curl_easy_perform会触发空指针崩溃
修复后的可运行参考代码
#include <iostream> #include <curl/curl.h> #include <cstring> // 替换为真实的发件人、收件人邮箱 static const char *payload_text = "Date: Mon, 29 Nov 2010 21:54:29 +1100\r\n" "To: <target@example.com>\r\n" "From: <youraccount@gmail.com>\r\n" "Message-ID: <dcd7cb36-11db-487a-9f3a-e652a9458efd@rfcpedant.example.org>\r\n" "Subject: SMTP example message\r\n" "\r\n" /* 空行分隔邮件头和正文,符合RFC5322要求 */ "The body of the message starts here.\r\n" "\r\n" "It could be a lot of lines, could be MIME encoded, whatever.\r\n" "Check RFC5322.\r\n"; // 跟踪数据读取进度的结构 struct ReadStatus { const char *data_ptr; size_t read_pos; size_t total_len; }; size_t read_function(char *buffer, size_t size, size_t nmemb, void *userp) { size_t max_write_len = size * nmemb; if (max_write_len == 0) return 0; ReadStatus *status = static_cast<ReadStatus*>(userp); size_t remain_len = status->total_len - status->read_pos; if (remain_len == 0) return 0; // 数据全部读完,返回0通知libcurl传输结束 size_t copy_len = remain_len > max_write_len ? max_write_len : remain_len; memcpy(buffer, status->data_ptr + status->read_pos, copy_len); status->read_pos += copy_len; return copy_len; } int main() { CURL *curl; CURLcode res = CURLE_OK; curl = curl_easy_init(); if(curl) { ReadStatus read_status; read_status.data_ptr = payload_text; read_status.read_pos = 0; read_status.total_len = strlen(payload_text); // 构造收件人链表 struct curl_slist *recipients = NULL; recipients = curl_slist_append(recipients, "<target@example.com>"); curl_easy_setopt(curl, CURLOPT_USERNAME, "youraccount@gmail.com"); // Gmail需使用开启两步验证后生成的16位应用专用密码,不能填账号登录密码 curl_easy_setopt(curl, CURLOPT_PASSWORD, "your_app_specific_password"); curl_easy_setopt(curl, CURLOPT_URL, "smtp://smtp.gmail.com:587"); curl_easy_setopt(curl, CURLOPT_MAIL_FROM, "<youraccount@gmail.com>"); curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients); curl_easy_setopt(curl, CURLOPT_READDATA, &read_status); curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_function); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL); res = curl_easy_perform(curl); if(res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } // 无论请求成功失败,都要释放所有分配的资源 curl_slist_free_all(recipients); curl_easy_cleanup(curl); } return 0; }
后续排查建议
- 你已经开启了
CURLOPT_VERBOSE,运行时直接看终端打印的SMTP交互日志,确认MAIL FROM、RCPT TO、DATA命令返回的都是2xx开头的成功状态码,如果返回5xx错误码根据提示调整参数即可 - 如果日志显示邮件已经提交到SMTP服务器但收件箱没找到,先检查垃圾邮件文件夹,没有配置SPF/DKIM签名的测试邮件大概率会被归类为垃圾邮件
- 如果登录阶段报错,确认邮箱已开启SMTP服务,Gmail账号必须开启两步验证后生成应用专用密码使用,不支持直接用账号密码登录SMTP
内容的提问来源于stack exchange,提问作者Franco
相关产品推荐
相关产品推荐

