從ESP32 / ESP8266開發板傳送電子郵件(一)

泰國Suwatchai K.先生寫了一個收發e-mail的Arduino程式庫,名叫“ESP Mail Client”(ESP郵件用戶端),支援ESP8266, ESP32以及採用SAMD21微控制器的Arduino開發板。本文將示範編寫一個從ESP32或ESP8266開發板傳送電子郵件的程式,在此之前,先認識一下寄信和收信的通訊協定。

電子郵件通訊協定:SMTP、POP3和IMAP

電子郵件並非直接寄送到收信者的手機或電腦裡面,而是寄到保管信件的郵件伺服器。郵件伺服器相當於郵局,信件會被轉送到收信人註冊帳號的那個郵件伺服器。

傳送和接收信件的通訊協定不一樣,我們不需要知道詳細的規範,但是必須認得它們的名字和用途。

  • SMTP協定:Simple Mail Transfer Protocol,直譯為「簡單郵件傳送協定」,負責從用戶端傳送信件到郵件伺服器,或者在郵件伺服器之間轉送信件。
  • POP3協定:Post Office Protocol version 3,直譯為「電子郵件協定第3版」,負責把信件從郵件伺服器下載到用戶端(如:電腦、手機)。
  • IMAP協定:Internet Message Access Protocol,直譯為「網際網路訊息存取協定」,同樣用於接收郵件,跟POP3的主要不同點是,POP3必須把整個信件下載到用戶端才能閱讀,IMAP則允許用戶端直接瀏覽郵件伺服器上的信件。

SMTP和IMAP協定

“ESP Mail Client”支援SMTP和IMAP兩種協定,具備寄信、收信和檢視信件功能。

安裝ESP Mail Client程式庫

選擇Arduino IDE的「草稿碼→匯入程式庫→管理程式碼」,開啟「程式庫管理員」,在其中輸入關鍵字“esp mail client”,可找到兩個程式庫。請安裝上面那一個;另一個“ESP32 Mail Client”是不再維護的舊版本。

安裝ESP Mail Client程式庫

郵件伺服器的「應用程式密碼」

我們可以連接現有的郵件伺服器讓ESP32/ESP8266寄送信件,前提是要取得這些資料:

  • 郵件伺服器的名稱
  • 埠號
  • 使用者帳號(也就是你的e-mail)
  • 使用者密碼

假設你有Gmail信箱帳號,就可以透過Gmail的伺服器送信:

  • SMTP伺服器名稱:smtp.gmail.com
    SMTP埠號:587

若你有微軟的live.com或hotmail.com等的信箱帳號,可透過office365.com的伺服器送信:

  • SMTP伺服器名稱:smtp.office365.com
    SMTP埠號:587

在2022年5月30日之前,我們可以直接使用Gmail帳號和密碼,讓第三方應用程式(如:Arduino開發板)傳送信件,但現在不行了,必須先在Gmail網站替應用程式產生一個專屬密碼,而在產生「應用程式密碼」之前,你的Gmail信箱必須有啟用兩步驟驗證

微軟的office365.com郵件伺服器也有類似的設定機制,底下是在Gmail的設定步驟示範,若要替office365.com郵件伺服器設定應用程式密碼,請點擊這個AppPassword連結

產生Gmail的應用程式密碼

進入你的Gmail頁面,點擊右上角的使用者圖示→點擊「管理你的Google帳戶」。

理你的Google帳戶

點擊左側的「安全性」,再點擊右下方的「應用程式密碼」。

應用程式密碼

點擊「選取應用程式→其他(自訂名稱)」。

自訂名稱

輸入自訂的應用程式名稱,這個名字不影響程式運作,可隨意設定。輸入名稱後,點擊「產生」。

自訂名稱

Gmail將產生一個專屬密碼,請複製它,稍後需要將它輸入到Arduino程式。

產生的密碼

透過ESP32/ESP8266開發板寄送電子郵件

前置作業準備就緒,回到Arduino IDE,底下是採用“ESP Mail Client”程式庫寄送e-mail的程式流程:

寄送e-mail的程式流程

這是經由office365.com郵件伺服器寄信到Yahoo信箱的郵件內容。

收到的信件

底下是寄信的完整程式碼,改自 “ESP Mail Client” 的Send_HTML範例程式

#if defined(ESP32)
  #include <WiFi.h>
#elif defined(ESP8266)
  #include <ESP8266WiFi.h>
#endif
#include <ESP_Mail_Client.h>

#define WIFI_SSID "你的Wi-Fi名稱"
#define WIFI_PASSWORD "Wi-Fi密碼"
// 採用Gmail的郵件伺服器
#define SMTP_HOST "smtp.gmail.com"
#define SMTP_PORT 465

#define AUTHOR_EMAIL "你的帳號@gmail.com"
#define AUTHOR_PASSWORD "上文產生的應用程式密碼"
#define SENDER_NAME "寄信人的名字"
#define MAIL_SUBJECT "信件的主旨" 

#define RECIPIENT "收信人的名字"
#define RECIPIENT_EMAIL "收信人的e-amil"

/* 宣告用於寄信的SMTP Session物件 */
SMTPSession smtp;

/* 取得寄信狀態的回呼函式 */
void smtpCallback(SMTP_Status status);

void setup(){
  Serial.begin(115200);
  Serial.print("\n連接Wi-Fi");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED){
    Serial.print(".");
    delay(500);
  }
  Serial.println("\nWi-Fi已連線。");
  Serial.println("IP位址:");
  Serial.println(WiFi.localIP());
  Serial.println();

  /** 是否在序列埠監控視窗顯示除錯訊息
   * 0代表不要
   * 1代表顯示簡單除錯訊息
  */
  smtp.debug(1);

  /* 設定取得寄信狀態的回呼函式 */
  smtp.callback(smtpCallback);

  /* 宣告SMTP郵件伺服器連線物件 */
  ESP_Mail_Session session;

  /* 設定SMTP郵件伺服器連線物件 */
  session.server.host_name = SMTP_HOST;      // 設定寄信的郵件伺服器名稱
  session.server.port = SMTP_PORT;           // 郵件伺服器的埠號
  session.login.email = AUTHOR_EMAIL;        // 你的帳號
  session.login.password = AUTHOR_PASSWORD;  // 密碼
  /* 設置時區 */
  session.time.ntp_server = F("pool.ntp.org,time.nist.gov");
  session.time.gmt_offset = 8;        // 台北時區
  session.time.day_light_offset = 0;  // 無日光節約時間

  /* 宣告SMTP訊息物件 */
  SMTP_Message message;

  /* 設定郵件標頭 */
  message.sender.name = SENDER_NAME;    // 寄信人的名字
  message.sender.email = AUTHOR_EMAIL;  // 寄信人的e-mail
  message.subject = MAIL_SUBJECT;       // 信件主旨
  message.addRecipient(RECIPIENT, RECIPIENT_EMAIL);  // "收信人的名字", "收信人的e-mail"

  /* 設定郵件內容(HTML格式訊息) */
  String htmlMsg = "<div style=\"color:#2f4468;\"><h1>學程式就是要動手實作</h1><p>- 從ESP32開發板傳送</p></div>";
  message.html.content = htmlMsg.c_str();  // 設定信件內容
  message.text.charSet = "utf-8";          // 設定訊息文字的編碼

  /* 連線到郵件伺服器 */
  if (!smtp.connect(&session))
    return;

  /* 開始寄信 */
  if (!MailClient.sendMail(&smtp, &message))
    Serial.println("寄信時出錯了:" + smtp.errorReason());
}

void loop(){

}

/* 取得信件寄送狀態的回呼函式本體 */
void smtpCallback(SMTP_Status status){
  /* 顯示目前的狀態 */
  Serial.println(status.info());

  /* 顯示傳送結果 */
  if (status.success()){
    Serial.println("----------------");
    ESP_MAIL_PRINTF("訊息傳送成功:%d\n", status.completedCount());
    ESP_MAIL_PRINTF("訊息傳送失敗:%d\n", status.failedCount());
    Serial.println("----------------\n");
    struct tm dt;

    for (int i = 0; i < smtp.sendingResult.size(); i++){
      SMTP_Result result = smtp.sendingResult.getItem(i);
      time_t ts = (time_t)result.timestamp;

      ESP_MAIL_PRINTF("訊息編號:%d\n", i + 1);
      ESP_MAIL_PRINTF("狀態:%s\n", result.completed ? "成功" : "失敗");
      ESP_MAIL_PRINTF("日期/時間:%s\n", asctime(localtime(&ts)));
      ESP_MAIL_PRINTF("收信人:%s\n", result.recipients.c_str());
      ESP_MAIL_PRINTF("主旨:%s\n", result.subject.c_str());
    }
    Serial.println("----------------\n");
    smtp.sendingResult.clear();
  }
}

Posts created 469

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top