精通 ESP32 SMTP | 使用 GMail 發送郵件
ESP32 SMTP 整合 Gmail 寄信功能是 IoT 專案中不可或缺的實用技能之一。本篇完整教學將涵蓋 Wi-Fi 連線、TLS 加密連線 Gmail、SMTP 命令交握、Base64 認證等技術細節,適合希望進一步強化 ESP32 應用能力的開發者。
想知道 ESP32 如何透過 SMTP 傳送 Gmail 郵件?這篇教學將帶你一步步從連接 Wi-Fi 到成功發送第一封郵件,完整解析 Gmail SMTP 認證、TLS 安全連線與寄件流程!

內容
什麼是 SMTP?
SMTP(Simple Mail Transfer Protocol,簡單郵件傳輸協定)是一種用來傳送電子郵件的網際網路標準協定。簡單來說,它就是電郵的「郵差」,負責把你的郵件從發件人這端,傳遞到收件人的郵件伺服器。
ESP32 是一款支援 Wi-Fi 的微控制器,我們就可以使用 ESP32 SMTP 搭配 Gmail 或其他郵件服務,讓 ESP32 自動寄出 Email。
開發環境
在開始編程之前,請確保已完成以下準備工作:
- 安裝 ESP-IDF 開發環境 (至少版本 v4.4 或更高)。
- ESP32 開發板。
配置 Wi-Fi 連接
ESP32 需要連接至一個 Wi-Fi 網路才能存取網際網路,並連線至 Gmail 的 SMTP 伺服器。
// WiFi credentials - replace with your actual network information
#define WIFI_SSID "your_wifi_ssid"
#define WIFI_PASS "your_wifi_password"
// Function to initialize and connect to WiFi in station mode
void wifi_init_sta() {
// Create default WiFi station network interface
esp_netif_create_default_wifi_sta();
// Initialize WiFi with default configuration
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Configure WiFi station settings
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID, // Set SSID (network name)
.password = WIFI_PASS, // Set WiFi password
.threshold.authmode = WIFI_AUTH_WPA2_PSK, // Minimum security protocol
},
};
// Set WiFi to station mode (client mode)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// Apply the WiFi configuration
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
// Start the WiFi service
ESP_ERROR_CHECK(esp_wifi_start());
// Initiate connection to the configured WiFi network
ESP_ERROR_CHECK(esp_wifi_connect());
}
Wi-Fi 事件處理
我們需要監聽 Wi-Fi 狀態變化,特別是獲得 IP 位址後啟動發送郵件的任務。
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT)
{
switch (event_id)
{
case WIFI_EVENT_STA_START:
ESP_LOGI(TAG, "WiFi station started");
break;
case WIFI_EVENT_STA_CONNECTED:
ESP_LOGI(TAG, "WiFi connected successfully");
break;
case WIFI_EVENT_STA_DISCONNECTED:
ESP_LOGI(TAG, "WiFi disconnected");
break;
}
}
else if (event_base == IP_EVENT)
{
if (event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
// Create SMTP task once we have an IP address
xTaskCreate(smtp_task, "smtp_task", SMTP_TASK_STACK_SIZE, NULL, 5, NULL);
}
}
}
建立 ESP32 SMTP 連接
我們將使用 Gmail 的 SMTP 伺服器進行郵件發送。Gmail 的 SMTP 伺服器地址為 smtp.gmail.com,端口為 465,使用 SSL/TLS 協議進行加密。
在建立連接時,ESP32 SMTP 需要支持 SSL/TLS,因此我們需要使用 esp_tls 庫。
Base64 編碼函數
ESP32 SMTP 認證過程中需要對郵箱和密碼進行 Base64 編碼。我們寫了簡單的 base64_encode 函數來完成這個操作。
char* base64_encode(const unsigned char *data, size_t input_length) {
const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t output_length = 4 * ((input_length + 2) / 3);
char *encoded_data = malloc(output_length + 1);
if (encoded_data == NULL) return NULL;
for (size_t i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? data[i++] : 0;
uint32_t octet_b = i < input_length ? data[i++] : 0;
uint32_t octet_c = i < input_length ? data[i++] : 0;
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded_data[j++] = base64_table[(triple >> 3 * 6) & 0x3F];
encoded_data[j++] = base64_table[(triple >> 2 * 6) & 0x3F];
encoded_data[j++] = base64_table[(triple >> 1 * 6) & 0x3F];
encoded_data[j++] = base64_table[(triple >> 0 * 6) & 0x3F];
}
for (size_t i = 0; i < (3 - input_length % 3) % 3; i++) {
encoded_data[output_length - 1 - i] = '=';
}
encoded_data[output_length] = '\0';
return encoded_data;
}
發送郵件函數
在郵件發送過程中,我們使用了 SSL/TLS 加密與 Gmail SMTP 伺服器進行通信,並透過 ESP32 SMTP 協議發送郵件。
void send_email() {
ESP_LOGI(TAG, "Starting SMTP connection, free heap: %d bytes", esp_get_free_heap_size());
// Configure TLS settings for secure SMTP connection
esp_tls_cfg_t tls_cfg = {
.timeout_ms = SMTP_TIMEOUT * 1000, // Connection timeout
.crt_bundle_attach = esp_crt_bundle_attach, // Use ESP32's certificate bundle
};
// Initialize TLS structure
esp_tls_t *tls = esp_tls_init();
if (!tls) {
ESP_LOGE(TAG, "Failed to initialize TLS");
return;
}
// Establish SSL connection to SMTP server (port 465 for SMTPS)
if (!esp_tls_conn_new_sync(SMTP_HOST, strlen(SMTP_HOST), SMTP_PORT, &tls_cfg, tls)) {
ESP_LOGE(TAG, "SSL connection failed");
esp_tls_conn_delete(tls);
return;
}
// SMTP protocol exchange
if (!smtp_exchange(tls, NULL, "220")) goto cleanup; // Wait for server greeting
if (!smtp_exchange(tls, "EHLO ESP32\r\n", "250")) goto cleanup; // Send EHLO
// Authentication process
char *auth_cmd = malloc(128);
if (!auth_cmd) goto cleanup;
// Initiate LOGIN authentication
if (!smtp_exchange(tls, "AUTH LOGIN\r\n", "334")) goto free_auth;
// Send base64-encoded username
char *encoded = base64_encode((const unsigned char *)SENDER_EMAIL, strlen(SENDER_EMAIL));
if (!encoded) goto free_auth;
snprintf(auth_cmd, 128, "%s\r\n", encoded);
free(encoded);
if (!smtp_exchange(tls, auth_cmd, "334")) goto free_auth;
// Send base64-encoded password
encoded = base64_encode((const unsigned char *)SENDER_PASSWORD, strlen(SENDER_PASSWORD));
if (!encoded) goto free_auth;
snprintf(auth_cmd, 128, "%s\r\n", encoded);
free(encoded);
if (!smtp_exchange(tls, auth_cmd, "235")) goto free_auth; // Expect authentication success
// Prepare email content
char *email_data = malloc(256);
if (!email_data) goto free_auth;
// Set sender
snprintf(email_data, 256, "MAIL FROM:<%s>\r\n", SENDER_EMAIL);
if (!smtp_exchange(tls, email_data, "250")) goto free_all;
// Set recipient
snprintf(email_data, 256, "RCPT TO:<%s>\r\n", RECIPIENT_EMAIL);
if (!smtp_exchange(tls, email_data, "250")) goto free_all;
// Begin data transmission
if (!smtp_exchange(tls, "DATA\r\n", "354")) goto free_all;
// Compose email headers and body
snprintf(email_data, 256,
"From: %s\r\nTo: %s\r\nSubject: ESP32 Test\r\n\r\n"
"Hello World!\r\n.\r\n", // The dot on a line by itself ends the message
SENDER_EMAIL, RECIPIENT_EMAIL);
// Send the actual email content
smtp_exchange(tls, email_data, "250"); // Expect 250 OK response
free_all:
free(email_data);
free_auth:
free(auth_cmd);
cleanup:
// Gracefully terminate the SMTP session
smtp_exchange(tls, "QUIT\r\n", NULL);
esp_tls_conn_delete(tls);
ESP_LOGI(TAG, "SMTP transaction completed");
}
Google 應用密碼
由於 Google 對於直接使用帳戶密碼進行第三方應用登入做了安全限制,尤其是在啟用了「兩步驟驗證」(2FA)功能的帳戶中,您必須使用 Google 應用密碼 來進行此類操作。這是一種專門為單一應用或設備生成的密碼,可以替代帳戶密碼進行身份驗證。
步驟 1:啟用 Google 兩步驗證
- 登入到 Google 帳戶:
- 訪問 Google 帳戶頁面,並登入您的 Google 帳戶。
- 啟用兩步驗證:
- 在「安全性」部分,啟用「兩步驗證」。
- 按照提示完成兩步驗證的設置,您需要綁定手機或其他驗證方式。
步驟 2:生成應用密碼
- 生成應用密碼:
- 在啟用兩步驗證後,返回到 Google 帳戶的「安全性」部分,找到 「應用密碼」。
- 點擊 「生成應用密碼」 按鈕。
- 選擇「應用」並選擇「其他(自定義名稱)」,然後輸入例如「ESP32 SMTP」作為名稱。
- 點擊生成,Google 會顯示一個 16 位的應用密碼(例如:
abcd efgh ijkl mnop
)。
- 複製應用密碼:
- 複製生成的應用密碼,這就是您在 ESP32 程式碼中需要使用的密碼。
步驟 3:在程式碼中使用應用密碼
在程式碼中,您將不再使用 Google 帳戶的登入密碼,而是使用剛剛生成的 應用密碼。在下面的程式碼範例中,您需要替換為您的應用密碼,而不是原本的 Google 帳戶密碼。
#define SENDER_PASSWORD "your_app_password" // Use generated app-specific password for Gmail
完整的 ESP32 SMTP 程式碼
最終的 ESP32 SMTP 完整程式碼如下:
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_tls.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "esp_crt_bundle.h"
// Configuration Section ============================================
#define WIFI_SSID "your_wifi_ssid" // Wi-Fi SSID
#define WIFI_PASS "your_wifi_password" // Wi-Fi password
#define SMTP_HOST "smtp.gmail.com" // Gmail SMTP server address
#define SMTP_PORT 465 // SMTP port (SSL)
#define SENDER_EMAIL "your_email@gmail.com" // Sender's Gmail address
#define SENDER_PASSWORD "your_app_password" // Gmail app password (generated in Google Account)
#define RECIPIENT_EMAIL "recipient_email@gmail.com" // Recipient's email address
#define SMTP_TIMEOUT 10 // SMTP timeout in seconds
#define SMTP_TASK_STACK_SIZE 8192 // Stack size for SMTP task
#define MAX_RETRIES 5 // Maximum retries for sending email
// ===============================================================
static const char *TAG = "SMTP_CLIENT";
static int s_retry_num = 0;
// Base64 encoding function
char* base64_encode(const unsigned char *data, size_t input_length) {
const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t output_length = 4 * ((input_length + 2) / 3);
char *encoded_data = malloc(output_length + 1);
if (encoded_data == NULL) return NULL;
for (size_t i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? data[i++] : 0;
uint32_t octet_b = i < input_length ? data[i++] : 0;
uint32_t octet_c = i < input_length ? data[i++] : 0;
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded_data[j++] = base64_table[(triple >> 3 * 6) & 0x3F];
encoded_data[j++] = base64_table[(triple >> 2 * 6) & 0x3F];
encoded_data[j++] = base64_table[(triple >> 1 * 6) & 0x3F];
encoded_data[j++] = base64_table[(triple >> 0 * 6) & 0x3F];
}
for (size_t i = 0; i < (3 - input_length % 3) % 3; i++) {
encoded_data[output_length - 1 - i] = '=';
}
encoded_data[output_length] = '\0';
return encoded_data;
}
// Function to handle SMTP communication with the server
bool smtp_exchange(esp_tls_t *tls, const char *send_str, const char *expect_resp) {
char *buf = malloc(256); // Allocate buffer for response
if (!buf) {
ESP_LOGE(TAG, "Failed to allocate buffer");
return false;
}
if (send_str != NULL) {
ESP_LOGI(TAG, "C: %s", send_str); // Log sent command
if (esp_tls_conn_write(tls, send_str, strlen(send_str)) < 0) {
free(buf);
return false;
}
}
int len = esp_tls_conn_read(tls, buf, 255);
if (len <= 0) {
free(buf);
return false;
}
buf[len] = '\0';
ESP_LOGI(TAG, "S: %.128s", buf); // Log server response (limited to 128 chars)
bool success = (expect_resp == NULL) || (strstr(buf, expect_resp) != NULL);
free(buf);
return success;
}
// Function to send email
void send_email() {
ESP_LOGI(TAG, "Starting SMTP connection, remaining heap: %d", esp_get_free_heap_size());
// Establish SSL connection
esp_tls_cfg_t tls_cfg = {
.timeout_ms = SMTP_TIMEOUT * 1000,
.crt_bundle_attach = esp_crt_bundle_attach,
};
esp_tls_t *tls = esp_tls_init();
if (!tls) {
ESP_LOGE(TAG, "Failed to initialize TLS");
return;
}
// Connect using SSL (port 465)
if (!esp_tls_conn_new_sync(SMTP_HOST, strlen(SMTP_HOST), SMTP_PORT, &tls_cfg, tls)) {
ESP_LOGE(TAG, "SSL connection failed");
esp_tls_conn_delete(tls);
return;
}
// SMTP protocol exchange
if (!smtp_exchange(tls, NULL, "220")) goto cleanup;
if (!smtp_exchange(tls, "EHLO ESP32\r\n", "250")) goto cleanup;
// Authentication process
char *auth_cmd = malloc(128);
if (!auth_cmd) goto cleanup;
if (!smtp_exchange(tls, "AUTH LOGIN\r\n", "334")) goto free_auth;
char *encoded = base64_encode((const unsigned char *)SENDER_EMAIL, strlen(SENDER_EMAIL));
if (!encoded) goto free_auth;
snprintf(auth_cmd, 128, "%s\r\n", encoded);
free(encoded);
if (!smtp_exchange(tls, auth_cmd, "334")) goto free_auth;
encoded = base64_encode((const unsigned char *)SENDER_PASSWORD, strlen(SENDER_PASSWORD));
if (!encoded) goto free_auth;
snprintf(auth_cmd, 128, "%s\r\n", encoded);
free(encoded);
if (!smtp_exchange(tls, auth_cmd, "235")) goto free_auth;
// Email content
char *email_data = malloc(256);
if (!email_data) goto free_auth;
snprintf(email_data, 256, "MAIL FROM:<%s>\r\n", SENDER_EMAIL);
if (!smtp_exchange(tls, email_data, "250")) goto free_all;
snprintf(email_data, 256, "RCPT TO:<%s>\r\n", RECIPIENT_EMAIL);
if (!smtp_exchange(tls, email_data, "250")) goto free_all;
if (!smtp_exchange(tls, "DATA\r\n", "354")) goto free_all;
snprintf(email_data, 256,
"From: %s\r\nTo: %s\r\nSubject: ESP32 Test\r\n\r\n"
"Hello World!\r\n.\r\n",
SENDER_EMAIL, RECIPIENT_EMAIL);
smtp_exchange(tls, email_data, "250");
free_all:
free(email_data);
free_auth:
free(auth_cmd);
cleanup:
smtp_exchange(tls, "QUIT\r\n", NULL);
esp_tls_conn_delete(tls);
ESP_LOGI(TAG, "SMTP process completed");
}
// SMTP task function
void smtp_task(void *pvParameters) {
ESP_LOGI(TAG, "SMTP task started");
// Send email
send_email();
vTaskDelete(NULL); // Task ends
}
// Wi-Fi event handler function
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT)
{
switch (event_id)
{
case WIFI_EVENT_STA_START:
ESP_LOGI(TAG, "WiFi STA started");
break;
case WIFI_EVENT_STA_CONNECTED:
ESP_LOGI(TAG, "WiFi connected successfully");
s_retry_num = 0;
break;
case WIFI_EVENT_STA_DISCONNECTED:
if (s_retry_num < MAX_RETRIES)
{
ESP_LOGI(TAG, "Attempting to reconnect (%d/%d)", s_retry_num + 1, MAX_RETRIES);
esp_wifi_connect();
s_retry_num++;
}
else
{
ESP_LOGE(TAG, "Connection failed, exceeded maximum retry attempts");
}
break;
}
}
else if (event_base == IP_EVENT)
{
if (event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
// Create SMTP task
xTaskCreate(smtp_task, "smtp_task", SMTP_TASK_STACK_SIZE, NULL, 5, NULL);
}
}
}
// Wi-Fi initialization
void wifi_init_sta() {
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Register event handler
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL));
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_connect());
}
void app_main() {
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_sta();
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
編譯和燒錄
完成程式碼後,您可以使用 ESP-IDF 提供的命令進行編譯、燒錄和監控。
檢查你的收件匣
在燒錄並執行程式後,請檢查你的郵件信箱,確認是否已收到 ESP32 裝置發送的測試郵件。
結論
透過本專案的完整實作,我們全面掌握了在 ESP32 平台上整合 SMTP 郵件服務的核心技術。這個 ESP32 SMTP 解決方案充分發揮了晶片的Wi-Fi和硬體加密加速優勢,採用TLS 安全協定建立與 ESP32 SMTP 伺服器的加密連線。在實作過程中,我們特別針對 ESP32 SMTP 協定堆疊進行了記憶體管理優化,透過分塊處理技術有效解決了資源受限環境下發送大型郵件的難題,同時保持了完整的 SMTP 協定相容性。