piątek, 23 stycznia 2026

[10] ESP32 - Wysyłanie Email

W tym poście opiszę jak wysyłać email przez ESP32.

Konfiguracja konta gmail:


Email będzie przesyłany z wykorzystaniem konta Gmail. 

Na samym początku na koncie google musimy włączyć uwierzytelnianie dwuetapowe:


To odblokuje możliwość dołożenia hasła do aplikacji:


Po wprowadzeniu nazwy wyświetli się hasło do aplikacji:


Należy je przepisać do programu z pominięciem spacji. 

Arduino:


W Arduino wysyłanie email wygląda następująco:

  1. #include <WiFi.h>
  2. #include <ESP_Mail_Client.h>
  3. #include <time.h>
  4.  
  5. const char* ssid = "SSID"; //SSID
  6. const char* password = "HASLO"; //PASS
  7.  
  8. //SMTP dla GMAIL
  9. #define SMTP_HOST "smtp.gmail.com"
  10. #define SMTP_PORT 465
  11.  
  12. //Adres email z którego przesyłamy dane
  13. #define AUTHOR_EMAIL "email_adres@gmail.com"
  14. //Hasło do aplikacji wygenerowane na koncie Google
  15. #define AUTHOR_PASSWORD "haslo_do_aplikacji"
  16. //Docelowy email na który zostanie wysłana wiadomość
  17. #define RECIPIENT_EMAIL "odbiorca@hsk.com.pl"
  18.  
  19. SMTPSession smtp;
  20.  
  21. void smtpCallback(SMTP_Status status);
  22.  
  23. void setup() {
  24.   Serial.begin(115200);
  25.   Serial.println();
  26.  
  27.   //Połączenie WIFI
  28.   WiFi.begin(ssid, password);
  29.   Serial.print("Łączenie z WiFi");
  30.   while (WiFi.status() != WL_CONNECTED) {
  31.     Serial.print(".");
  32.     delay(500);
  33.   }
  34.   Serial.println("Połączono z WiFi!");
  35.   Serial.print("IP: ");
  36.   Serial.println(WiFi.localIP());
  37.  
  38.   //pobranie czasu z NTP
  39.   configTime(0, 0, "pool.ntp.org", "time.nist.gov");  
  40.   //Strefa PL
  41.   setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);
  42.   tzset();
  43.  
  44.   //czekanie na ustawienie czasu
  45.   Serial.print("Ustawianie czasu\n");
  46.   time_t now = time(nullptr);
  47.   while (now < 1700000000) {
  48.     Serial.print(".");
  49.     delay(500);
  50.     now = time(nullptr);
  51.   }
  52.   Serial.println("Czas ustawiony!");
  53.  
  54.   smtp.debug(1);
  55.   smtp.callback(smtpCallback);
  56.  
  57.   ESP_Mail_Session session;
  58.   session.server.host_name = SMTP_HOST;
  59.   session.server.port = SMTP_PORT;
  60.   session.login.email = AUTHOR_EMAIL;
  61.   session.login.password = AUTHOR_PASSWORD;
  62.   session.login.user_domain = "";
  63.  
  64.   //Tworzenie wiadomości
  65.   SMTP_Message message;
  66.   message.sender.name = "ESP32";
  67.   message.sender.email = AUTHOR_EMAIL;
  68.  
  69.   //Nagłówek
  70.   message.subject = "ESP32 test wiadomosci";
  71.   message.addRecipient("Odbiorca", RECIPIENT_EMAIL);
  72.  
  73.   //Treść emaila
  74.   String textMsg = "Treść emaila - testowa wiadomosc";
  75.   message.text.content = textMsg.c_str();
  76.   message.text.charSet = "utf-8";
  77.   message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit;
  78.  
  79.   //Połączenie i wysyłanie
  80.   if (!smtp.connect(&session)) {
  81.     Serial.println("Błąd połączenia SMTP!");
  82.     return;
  83.   }
  84.  
  85.   if (!MailClient.sendMail(&smtp, &message)) {
  86.     Serial.print("Błąd wysyłania: \n");
  87.     Serial.println(smtp.errorReason());
  88.   } else {
  89.     Serial.println("E-mail wysłany!\n");
  90.   }
  91. }
  92.  
  93. void loop() { }
  94.  
  95. void smtpCallback(SMTP_Status status) {
  96.   Serial.println(status.info());
  97.   if (status.success()) {
  98.     Serial.println("Status wiadomości: wysłano poprawnie");
  99.   }
  100. }

Tutaj nawiązywane jest połączenie z WiFi, pobierany aktualny czas z NTP oraz ustawiana jest aktualna strefa czasowa. Następnie konfigurowana jest sesja SMTP z adresem serwera, portem, danymi logowania, hasła. Dalej tworzona jest wiadomość email z nadawcą, tematem, odbiorcą oraz treścią w formacie UTF-8. W kolejnym krotku nawiązywane jest połączenie TLS z serwerem Gmail. Automatycznie wykonywane jest uwierzytlnianie SMTP. Po czym następuje wysłanie wiadomości. 
Callback (smtpCallback) zwraca status przesłania wiadomości. 

ESP-IDF:


Drugi przykład będzie wykorzystywał ESP-IDF. 

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <time.h>
  4.  
  5. #include "freertos/FreeRTOS.h"
  6. #include "freertos/event_groups.h"
  7. #include "freertos/task.h"
  8.  
  9. #include "esp_timer.h"
  10. #include "esp_event.h"
  11. #include "esp_log.h"
  12. #include "esp_netif.h"
  13. #include "esp_system.h"
  14. #include "esp_tls.h"
  15. #include "esp_crt_bundle.h"
  16.  
  17. #include "nvs_flash.h"
  18. #include "esp_wifi.h"
  19.  
  20. #include "mbedtls/base64.h"
  21. #include "lwip/apps/sntp.h"
  22.  
  23. static const char *TAG = "APP";
  24.  
  25. static EventGroupHandle_t s_wifi_ev;
  26. static const int WIFI_CONNECTED_BIT = BIT0;
  27.  
  28. static const char *WIFI_SSID = "SSID";
  29. static const char *WIFI_PASS = "HASLO";
  30.  
  31. // ====== SMTP (GMAIL SSL 465) ======
  32. #define SMTP_HOST "smtp.gmail.com"
  33. #define SMTP_PORT 465
  34.  
  35. #define AUTHOR_EMAIL     "ADRES_EMAIL@gmail.com"
  36. #define AUTHOR_PASSWORD  "HASLO_DO_APLIKACJI"
  37. #define RECIPIENT_EMAIL  "RECEIVER@adres.pl"
  38.  
  39. static int b64_encode(char *out, size_t out_sz, const char *in)
  40. {
  41.     size_t olen = 0;
  42.     int rc = mbedtls_base64_encode((unsigned char *)out, out_sz, &olen,
  43.                                   (const unsigned char *)in, strlen(in));
  44.     if (rc != 0) return -1;
  45.     out[olen] = 0;
  46.     return (int)olen;
  47. }
  48.  
  49. static int tls_write_all(struct esp_tls *tls, const char *data)
  50. {
  51.     int to_write = (int)strlen(data);
  52.     int written = 0;
  53.  
  54.     while (written < to_write) {
  55.         int r = esp_tls_conn_write(tls, data + written, to_write - written);
  56.         if (r <= 0) return -1;
  57.         written += r;
  58.     }
  59.     return 0;
  60. }
  61.  
  62. static int tls_read_line(struct esp_tls *tls, char *buf, size_t buf_sz, int timeout_ms)
  63. {
  64.     size_t pos = 0;
  65.     int64_t start = esp_timer_get_time();
  66.  
  67.     while (pos + 1 < buf_sz) {
  68.         char c;
  69.         int r = esp_tls_conn_read(tls, &c, 1);
  70.         if (r == 1) {
  71.             buf[pos++] = c;
  72.             if (c == '\n') break;
  73.         } else {
  74.             int64_t now = esp_timer_get_time();
  75.             if ((now - start) / 1000 > timeout_ms) break;
  76.             vTaskDelay(pdMS_TO_TICKS(10));
  77.         }
  78.     }
  79.     buf[pos] = 0;
  80.     return (int)pos;
  81. }
  82.  
  83. static int smtp_expect(struct esp_tls *tls, const char *prefix)
  84. {
  85.     char line[512];
  86.     for (;;) {
  87.         int n = tls_read_line(tls, line, sizeof(line), 5000);
  88.         if (n <= 0) return -1;
  89.  
  90.         ESP_LOGI(TAG, "S: %s", line);
  91.  
  92.         // SMTP multiline: "250-" ... kończy się na "250 "
  93.         if (strlen(line) >= 4 && line[3] == ' ') {
  94.             if (!prefix) return 0;
  95.             return (strncmp(line, prefix, strlen(prefix)) == 0) ? 0 : -1;
  96.         }
  97.     }
  98. }
  99.  
  100. static int smtp_send_cmd(struct esp_tls *tls, const char *cmd, const char *expect_prefix)
  101. {
  102.     ESP_LOGI(TAG, "C: %s", cmd);
  103.     if (tls_write_all(tls, cmd) != 0) return -1;
  104.     return smtp_expect(tls, expect_prefix);
  105. }
  106.  
  107. static int smtp_send_gmail_ssl(void)
  108. {
  109.     ESP_LOGI(TAG, "Łączenie TLS do %s:%d ...", SMTP_HOST, SMTP_PORT);
  110.  
  111.     esp_tls_cfg_t cfg = {
  112.         .crt_bundle_attach = esp_crt_bundle_attach,
  113.         .timeout_ms = 10000,
  114.     };
  115.  
  116.     struct esp_tls *tls = esp_tls_init();
  117.     if (!tls) return -1;
  118.  
  119.     int rc = esp_tls_conn_new_sync(SMTP_HOST, (int)strlen(SMTP_HOST), SMTP_PORT, &cfg, tls);
  120.     if (rc != 1) {
  121.         ESP_LOGE(TAG, "TLS connect error: %d", rc);
  122.         esp_tls_conn_destroy(tls);
  123.         return -1;
  124.     }
  125.  
  126.     if (smtp_expect(tls, "220") != 0) goto fail;
  127.     if (smtp_send_cmd(tls, "EHLO esp32\r\n", "250") != 0) goto fail;
  128.     if (smtp_send_cmd(tls, "AUTH LOGIN\r\n", "334") != 0) goto fail;
  129.  
  130.     char b64[256];
  131.  
  132.     if (b64_encode(b64, sizeof(b64), AUTHOR_EMAIL) < 0) goto fail;
  133.     char line1[300];
  134.     snprintf(line1, sizeof(line1), "%s\r\n", b64);
  135.     if (smtp_send_cmd(tls, line1, "334") != 0) goto fail;
  136.  
  137.     if (b64_encode(b64, sizeof(b64), AUTHOR_PASSWORD) < 0) goto fail;
  138.     char line2[300];
  139.     snprintf(line2, sizeof(line2), "%s\r\n", b64);
  140.     if (smtp_send_cmd(tls, line2, "235") != 0) goto fail;
  141.  
  142.     char cmd[300];
  143.     snprintf(cmd, sizeof(cmd), "MAIL FROM:<%s>\r\n", AUTHOR_EMAIL);
  144.     if (smtp_send_cmd(tls, cmd, "250") != 0) goto fail;
  145.  
  146.     snprintf(cmd, sizeof(cmd), "RCPT TO:<%s>\r\n", RECIPIENT_EMAIL);
  147.     if (smtp_send_cmd(tls, cmd, "250") != 0) goto fail;
  148.  
  149.     if (smtp_send_cmd(tls, "DATA\r\n", "354") != 0) goto fail;
  150.  
  151.     const char *subject = "ESP32 test wiadomosci";
  152.     const char *body    = "Treść emaila - testowa wiadomosc";
  153.  
  154.     char datebuf[64];
  155.     time_t now = time(NULL);
  156.     struct tm tm_now;
  157.     localtime_r(&now, &tm_now);
  158.     strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S %z", &tm_now);
  159.  
  160.     char msg[1500];
  161.     snprintf(msg, sizeof(msg),
  162.              "From: ESP32 <%s>\r\n"
  163.              "To: <%s>\r\n"
  164.              "Subject: %s\r\n"
  165.              "Date: %s\r\n"
  166.              "MIME-Version: 1.0\r\n"
  167.              "Content-Type: text/plain; charset=utf-8\r\n"
  168.              "Content-Transfer-Encoding: 7bit\r\n"
  169.              "\r\n"
  170.              "%s\r\n"
  171.              "\r\n.\r\n",
  172.              AUTHOR_EMAIL,
  173.              RECIPIENT_EMAIL,
  174.              subject,
  175.              datebuf,
  176.              body);
  177.  
  178.     ESP_LOGI(TAG, "C: [DATA] (%d bytes)", (int)strlen(msg));
  179.     if (tls_write_all(tls, msg) != 0) goto fail;
  180.     if (smtp_expect(tls, "250") != 0) goto fail;
  181.  
  182.     smtp_send_cmd(tls, "QUIT\r\n", "221");
  183.  
  184.     esp_tls_conn_destroy(tls);
  185.     ESP_LOGI(TAG, "E-mail wysłany!");
  186.     return 0;
  187.  
  188. fail:
  189.     ESP_LOGE(TAG, "Błąd SMTP/TLS");
  190.     esp_tls_conn_destroy(tls);
  191.     return -1;
  192. }
  193.  
  194. //SNTP
  195. static void sntp_init_and_wait(void)
  196. {
  197.     setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);
  198.     tzset();
  199.  
  200.     ESP_LOGI(TAG, "Ustawianie czasu (SNTP) ...");
  201.  
  202.     sntp_setoperatingmode(SNTP_OPMODE_POLL);
  203.     sntp_setservername(0, "pool.ntp.org");
  204.     sntp_setservername(1, "time.nist.gov");
  205.     sntp_init();
  206.  
  207.     time_t now = 0;
  208.     while (now < 1700000000) {
  209.         vTaskDelay(pdMS_TO_TICKS(500));
  210.         now = time(NULL);
  211.     }
  212.     ESP_LOGI(TAG, "Czas ustawiony.");
  213. }
  214.  
  215. static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
  216. {
  217.     if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
  218.         esp_wifi_connect();
  219.         return;
  220.     }
  221.     if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
  222.         esp_wifi_connect();
  223.         return;
  224.     }
  225.     if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
  226.         xEventGroupSetBits(s_wifi_ev, WIFI_CONNECTED_BIT);
  227.         return;
  228.     }
  229. }
  230.  
  231. static void wifi_init_sta(void)
  232. {
  233.     s_wifi_ev = xEventGroupCreate();
  234.  
  235.     ESP_ERROR_CHECK(esp_netif_init());
  236.     ESP_ERROR_CHECK(esp_event_loop_create_default());
  237.     esp_netif_create_default_wifi_sta();
  238.  
  239.     wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  240.     ESP_ERROR_CHECK(esp_wifi_init(&cfg));
  241.  
  242.     ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
  243.     ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL));
  244.  
  245.     wifi_config_t wifi_config = {0};
  246.     strncpy((char *)wifi_config.sta.ssid, WIFI_SSID, sizeof(wifi_config.sta.ssid));
  247.     strncpy((char *)wifi_config.sta.password, WIFI_PASS, sizeof(wifi_config.sta.password));
  248.     wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
  249.  
  250.     ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
  251.     ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
  252.     ESP_ERROR_CHECK(esp_wifi_start());
  253.  
  254.     ESP_LOGI(TAG, "Łączenie z WiFi ...");
  255.     xEventGroupWaitBits(s_wifi_ev, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
  256.     ESP_LOGI(TAG, "Połączono z WiFi.");
  257. }
  258.  
  259. static void email_task(void *arg)
  260. {
  261.     wifi_init_sta();
  262.     sntp_init_and_wait();
  263.  
  264.     smtp_send_gmail_ssl();
  265.  
  266.     vTaskDelete(NULL);
  267. }
  268.  
  269. void app_main(void)
  270. {
  271.     ESP_ERROR_CHECK(nvs_flash_init());
  272.  
  273.     // stack ustawiony na 16 KB
  274.     xTaskCreate(email_task, "email_task", 16 * 1024, NULL, 5, NULL);
  275. }

Głównym elementem powyższego programu jest email_task. Na samym początku wykonywana jest konfiguracja WiFi w trybie STA. Program oczekuje na adres IP. Po uzyskaniu dostępu do internetu uruchamiany jest SNTP, w celu ustawienia poprawnego czasu systemowego. TLS wymaga poprawnej daty w celu weryfikacji certyfikatów serwera. 
Dalej przechodzi do głównego zadania czyli wysłania emaila. ESP nawiązuje bezpieczne połączenie TLS z serwerem smtp.gmail.com po porcie 465. Po zestawieniu połączenia wykonynywany jest klasyczny SMTP:

W programie nagłówki są budowane ręcznie wraz z treścią wiadomości. Wiadomość kończymy sekwencją \r\n.\r\n. Tak jak w protokole SMTP. 

Do serwera dane wysyłane są przez TLS w pętli. Odpowiedź SMTP czytana jest linia po linii. Każdy z etapów sprawdza poprawny kod odpowiedzi. Po przesłaniu wiadomości program zamyka wszystko komendą QUIT. Czyszczone są zasoby TLS. Następnie task kończy działanie.