Reading emails using IMAP - how to access the text in the mail

Hi all - I have successfully used the example code to read a mail sent to the ESP32, but I want to act upon the text of the email - for example, if the text is YES do something, and if the text is NO do something else. Basically I want to do very basic control of a device by sending it an email.

The example code that I used is pasted below with a very simple addition by me right at the end (the last 7 lines) , where I tried (but failed) to use the text of the message to print YES or NO.

Can anyone point me in the right direction as to where I am going wrong? The code as below always prints "It found NO" regardless of what I write in the body of the email message.

If there is a more elegant way of using an email to send a control message to an ESP32 project, please let me know!

By the way - the code below included lines for the use of a SD card which I am not using so I commented them out.

/**
 * Created by K. Suwatchai (Mobizt)
 *
 * Email: suwatchai@outlook.com
 *
 * Github: https://github.com/mobizt/ESP-Mail-Client
 *
 * Copyright (c) 2023 mobizt
 */

// This example shows how to read Email and store the message in SD card.

/** Note for library update from v2.x.x to v3.x.x.
 *
 *  Struct data names changed
 *
 * "ESP_Mail_Session" changes to "Session_Config"
 * "IMAP_Config" changes to "IMAP_Data"
 *
 * Changes in the examples
 *
 * ESP_Mail_Session session;
 * to
 * Session_Config config;
 *
 * IMAP_Config config;
 * to
 * IMAP_Data imap_data;
 */

/** Assign SD card type and FS used in src/ESP_Mail_FS.h and
 * change the config for that card interfaces in src/extras/SDHelper.h
 */

#include <Arduino.h>
#if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W)
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#elif __has_include(<WiFiNINA.h>)
#include <WiFiNINA.h>
#elif __has_include(<WiFi101.h>)
#include <WiFi101.h>
#elif __has_include(<WiFiS3.h>)
#include <WiFiS3.h>
#endif

#include <ESP_Mail_Client.h>

// Provide the SD card interfaces setting and mounting
//#include <extras/SDHelper.h>

#define WIFI_SSID "***********"  //Elmtree House wifi
#define WIFI_PASSWORD "**********"

/* The imap host name e.g. imap.gmail.com for GMail or outlook.office365.com for Outlook */
#define IMAP_HOST "imap.gmail.com" //was "<imap.gmail.com>", which is wrong!

/** The imap port e.g.
 * 143  or esp_mail_imap_port_143
 * 993 or esp_mail_imap_port_993
 */
#define IMAP_PORT esp_mail_imap_port_993 //993

/* The sign in credentials */
#define AUTHOR_EMAIL "************"
#define AUTHOR_PASSWORD "*********" 

/* Callback function to get the Email reading status */
void imapCallback(IMAP_Status status);

/* Print the list of mailbox folders */
void printAllMailboxesInfo(IMAPSession &imap);

/* Print the selected folder info */
void printSelectedMailboxInfo(SelectedFolderInfo sFolder);

/* Print all messages from the message list */
void printMessages(std::vector<IMAP_MSG_Item> &msgItems, bool headerOnly);

/* Print all attachments info from the message */
void printAttacements(std::vector<IMAP_Attach_Item> &atts);

/* Declare the global used IMAPSession object for IMAP transport */
IMAPSession imap;

#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
WiFiMulti multi;
#endif

void setup()
{

    Serial.begin(9600);

#if defined(ARDUINO_ARCH_SAMD)
    while (!Serial)
        ;
#endif

    Serial.println();

#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
    multi.addAP(WIFI_SSID, WIFI_PASSWORD);
    multi.run();
#else
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
#endif

    Serial.print("Connecting to Wi-Fi");

#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
    unsigned long ms = millis();
#endif

    while (WiFi.status() != WL_CONNECTED)
    {
        Serial.print(".");
        delay(300);
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
        if (millis() - ms > 10000)
            break;
#endif
    }
    Serial.println();
    Serial.print("Connected with IP: ");
    Serial.println(WiFi.localIP());
    Serial.println();

    /*  Set the network reconnection option */
    MailClient.networkReconnect(true);

    // The WiFi credentials are required for Pico W
    // due to it does not have reconnect feature.
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
    MailClient.clearAP();
    MailClient.addAP(WIFI_SSID, WIFI_PASSWORD);
#endif

//#if defined(ESP_MAIL_DEFAULT_SD_FS) // defined in src/ESP_Mail_FS.h
    // Mount SD card.
 //   SD_Card_Mounting(); // See src/extras/SDHelper.h
//#endif

    /** Enable the debug via Serial port
     * 0 for no debugging
     * 1 for basic level debugging
     *
     * Debug port can be changed via ESP_MAIL_DEFAULT_DEBUG_PORT in ESP_Mail_FS.h
     */
    imap.debug(1);

    /* Set the callback function to get the reading results */
    imap.callback(imapCallback);

    /** In case the SD card/adapter was used for the file storagge, the SPI pins can be configure from
     * MailClient.sdBegin function which may be different for ESP32 and ESP8266
     * For ESP32, assign all of SPI pins
     * MailClient.sdBegin(14,2,15,13)
     * Which SCK = 14, MISO = 2, MOSI = 15 and SS = 13
     * And for ESP8266, assign the CS pins of SPI port
     * MailClient.sdBegin(15)
     * Which pin 15 is the CS pin of SD card adapter
     */

    /* Declare the Session_Config for user defined session credentials */
    Session_Config config;

    /* Set the session config */
    config.server.host_name = IMAP_HOST;
    config.server.port = IMAP_PORT;
    config.login.email = AUTHOR_EMAIL;
    config.login.password = AUTHOR_PASSWORD;

    /* Define the IMAP_Data object used for user defined IMAP operating options. */
    IMAP_Data imap_data;

    /* Set the storage to save the downloaded files and attachments */
    imap_data.storage.saved_path = F("/email_data");

    /** The file storage type e.g.
     * esp_mail_file_storage_type_none,
     * esp_mail_file_storage_type_flash, and
     * esp_mail_file_storage_type_sd
     */
    imap_data.storage.type = esp_mail_file_storage_type_sd;

    /** Set to download headers, text and html messaeges,
     * attachments and inline images respectively.
     */
    imap_data.download.header = true;
    imap_data.download.text = true;
    imap_data.download.html = true;
    imap_data.download.attachment = true;
    imap_data.download.inlineImg = true;

    /** Set to enable the results i.e. html and text messaeges
     * which the content stored in the IMAPSession object is limited
     * by the option imap_data.limit.msg_size.
     * The whole message can be download through imap_data.download.text
     * or imap_data.download.html which not depends on these enable options.
     */
    imap_data.enable.html = true;
    imap_data.enable.text = true;

    /* Set to enable the sort the result by message UID in the decending order */
    imap_data.enable.recent_sort = true;

    /* Set to report the download progress via the default serial port */
    imap_data.enable.download_status = true;

    /* Header fields parsing is case insensitive by default to avoid uppercase header in some server e.g. iCloud
    , to allow case sensitive parse, uncomment below line*/
    // imap_data.enable.header_case_sensitive = true;

    /* Set the limit of number of messages in the search results */
    imap_data.limit.search = 5;

    /** Set the maximum size of message stored in
     * IMAPSession object in byte
     */
    imap_data.limit.msg_size = 512;

    /** Set the maximum attachments and inline images files size
     * that can be downloaded in byte.
     * The file which its size is largger than this limit may be saved
     * as truncated file.
     */
    imap_data.limit.attachment_size = 1024 * 1024 * 5;

    // If ID extension was supported by IMAP server, assign the client identification
    // name, version, vendor, os, os_version, support_url, address, command, arguments, environment
    // Server ID can be optained from imap.serverID() after calling imap.connect and imap.id.

    // imap_data.identification.name = "User";
    // imap_data.identification.version = "1.0";

    /* Set the TCP response read timeout in seconds */
    // imap.setTCPTimeout(10);

    /* Connect to the server */
    if (!imap.connect(&config, &imap_data))
    {
        MailClient.printf("Connection error, Error Code: %d, Reason: %s\n", imap.errorCode(), imap.errorReason().c_str());
        return;
    }

    /** Or connect without log in and log in later

      if (!imap.connect(&config, &imap_data, false))
        return;

      if (!imap.loginWithPassword(AUTHOR_EMAIL, AUTHOR_PASSWORD))
        return;
    */

    // Client identification can be sent to server later with
    /**
     * IMAP_Identification iden;
     * iden.name = "user";
     * iden.version = "1.0";
     *
     * if (imap.id(&iden))
     * {
     *    Serial.println("\nSend Identification success");
     *    Serial.println(imap.serverID());
     * }
     * else
     *    MailClient.printf("nIdentification sending error, Error Code: %d, Reason: %s", imap.errorCode(), imap.errorReason().c_str());
     */

    if (!imap.isLoggedIn())
    {
        Serial.println("Not yet logged in.");
    }
    else
    {
        if (imap.isAuthenticated())
            Serial.println("Successfully logged in.");
        else
            Serial.println("Connected with no Auth.");
    }

    /*  {Optional} */
    printAllMailboxesInfo(imap);

    /* Open or select the mailbox folder to read or search the message */
    if (!imap.selectFolder(F("INBOX")))
        return;

    /*  {Optional} */
    printSelectedMailboxInfo(imap.selectedFolder());

    /** Message UID to fetch or read e.g. 100.
     * In this case we will get the UID from the max message number (lastest message)
     */
    imap_data.fetch.uid = imap.getUID(imap.selectedFolder().msgCount());

    // if IMAP server supports CONDSTORE extension, the modification sequence can be assign to fetch command
    // Note that modsequence value supports in this library is 32-bit integer
    imap_data.fetch.modsequence = 123;

    // To fetch only header part
    // imap_data.fetch.headerOnly = true;

    // or fetch via the message sequence number
    // imap_data.fetch.number = imap.selectedFolder().msgCount();

    // if both imap_data.fetch.uid and imap_data.fetch.number were set,
    // then total 2 messages will be fetched i.e. one using uid and other using number.

    /* Set seen flag */

    // The message with "Seen" flagged means the message was already read or seen by user.
    // The default value of this option is set to false.
    // If you want to set the message flag as "Seen", set this option to true.
    // If this option is false, the message flag was unchanged.
    // To set or remove flag from message, see Set_Flags.ino example.

    // imap_data.fetch.set_seen = true;

    /* Fetch or read only message header */
    // imap_data.fetch.headerOnly = true;

    /* Read or search the Email and close the session */

    // When message was fetched or read, the /Seen flag will not set or message remained in unseen or unread status,
    // as this is the purpose of library (not UI application), user can set the message status as read by set \Seen flag
    // to message, see the Set_Flags.ino example.
    MailClient.readMail(&imap);

    /* Clear all stored data in IMAPSession object */
    imap.empty();
}

void loop()
{
}

/* Callback function to get the Email reading status */
void imapCallback(IMAP_Status status)
{
    /* Print the current status */
    Serial.println(status.info());

    /* Show the result when reading finished */
    if (status.success())
    {
        /* Print the result */
        /* Get the message list from the message list data */
        IMAP_MSG_List msgList = imap.data();
        printMessages(msgList.msgItems, imap.headerOnly());

        /* Clear all stored data in IMAPSession object */
        imap.empty();
    }
}

void printAllMailboxesInfo(IMAPSession &imap)
{
    /* Declare the folder collection class to get the list of mailbox folders */
    FoldersCollection folders;

    /* Get the mailbox folders */
    if (imap.getFolders(folders))
    {
        for (size_t i = 0; i < folders.size(); i++)
        {
            /* Iterate each folder info using the  folder info item data */
            FolderInfo folderInfo = folders.info(i);
            MailClient.printf("%s%s%s", i == 0 ? "\nAvailable folders: " : ", ", folderInfo.name, i == folders.size() - 1 ? "\n" : "");
        }
    }
}

void printSelectedMailboxInfo(SelectedFolderInfo sFolder)
{
    /* Show the mailbox info */
    MailClient.printf("\nInfo of the selected folder\nTotal Messages: %d\n", sFolder.msgCount());
    MailClient.printf("UID Validity: %d\n", sFolder.uidValidity());
    MailClient.printf("Predicted next UID: %d\n", sFolder.nextUID());
    if (sFolder.unseenIndex() > 0)
        MailClient.printf("First Unseen Message Number: %d\n", sFolder.unseenIndex());
    else
        MailClient.printf("Unseen Messages: No\n");

    if (sFolder.modSeqSupported())
        MailClient.printf("Highest Modification Sequence: %llu\n", sFolder.highestModSeq());
    for (size_t i = 0; i < sFolder.flagCount(); i++)
        MailClient.printf("%s%s%s", i == 0 ? "Flags: " : ", ", sFolder.flag(i).c_str(), i == sFolder.flagCount() - 1 ? "\n" : "");

    if (sFolder.flagCount(true))
    {
        for (size_t i = 0; i < sFolder.flagCount(true); i++)
            MailClient.printf("%s%s%s", i == 0 ? "Permanent Flags: " : ", ", sFolder.flag(i, true).c_str(), i == sFolder.flagCount(true) - 1 ? "\n" : "");
    }
}

void printAttacements(std::vector<IMAP_Attach_Item> &atts)
{
    MailClient.printf("Attachment: %d file(s)\n****************************\n", atts.size());
    for (size_t j = 0; j < atts.size(); j++)
    {
        IMAP_Attach_Item att = atts[j];
        /** att.type can be
         * esp_mail_att_type_none or 0
         * esp_mail_att_type_attachment or 1
         * esp_mail_att_type_inline or 2
         */
        MailClient.printf("%d. Filename: %s, Name: %s, Size: %d, MIME: %s, Type: %s, Description: %s, Creation Date: %s\n", j + 1, att.filename, att.name, att.size, att.mime, att.type == esp_mail_att_type_attachment ? "attachment" : "inline", att.description, att.creationDate);
    }
    Serial.println();
}

void printMessages(std::vector<IMAP_MSG_Item> &msgItems, bool headerOnly)
{

    /** In devices other than ESP8266 and ESP32, if SD card was chosen as filestorage and
     * the standard SD.h library included in ESP_Mail_FS.h, files will be renamed due to long filename
     * (> 13 characters) is not support in the SD.h library.
     * To show how its original file name, use imap.fileList().
     */
    // Serial.println(imap.fileList());

    for (size_t i = 0; i < msgItems.size(); i++)
    {

        /* Iterate to get each message data through the message item data */
        IMAP_MSG_Item msg = msgItems[i];

        Serial.println("****************************");
        MailClient.printf("Number: %d\n", msg.msgNo);
        MailClient.printf("UID: %d\n", msg.UID);

        // The attachment status in search may be true in case the "multipart/mixed"
        // content type header was set with no real attachtment included.
        MailClient.printf("Attachment: %s\n", msg.hasAttachment ? "yes" : "no");

        MailClient.printf("Messsage-ID: %s\n", msg.ID);

        if (strlen(msg.flags))
            MailClient.printf("Flags: %s\n", msg.flags);
        if (strlen(msg.acceptLang))
            MailClient.printf("Accept Language: %s\n", msg.acceptLang);
        if (strlen(msg.contentLang))
            MailClient.printf("Content Language: %s\n", msg.contentLang);
        if (strlen(msg.from))
            MailClient.printf("From: %s\n", msg.from);
        if (strlen(msg.sender))
            MailClient.printf("Sender: %s\n", msg.sender);
        if (strlen(msg.to))
            MailClient.printf("To: %s\n", msg.to);
        if (strlen(msg.cc))
            MailClient.printf("CC: %s\n", msg.cc);
        if (strlen(msg.bcc))
            MailClient.printf("BCC: %s\n", msg.bcc);
        if (strlen(msg.date))
        {
            MailClient.printf("Date: %s\n", msg.date);
            MailClient.printf("Timestamp: %d\n", (int)MailClient.Time.getTimestamp(msg.date));
        }
        if (strlen(msg.subject))
            MailClient.printf("Subject: %s\n", msg.subject); //This prints the Subject line of the received email
        if (strlen(msg.reply_to))
            MailClient.printf("Reply-To: %s\n", msg.reply_to);
        if (strlen(msg.return_path))
            MailClient.printf("Return-Path: %s\n", msg.return_path);
        if (strlen(msg.in_reply_to))
            MailClient.printf("In-Reply-To: %s\n", msg.in_reply_to);
        if (strlen(msg.references))
            MailClient.printf("References: %s\n", msg.references);
        if (strlen(msg.comments))
            MailClient.printf("Comments: %s\n", msg.comments);
        if (strlen(msg.keywords))
            MailClient.printf("Keywords: %s\n", msg.keywords);

        /* If the result contains the message info (Fetch mode) */
        if (!headerOnly)
        {
            if (strlen(msg.text.content))
                MailClient.printf("Text Message: %s\n", msg.text.content);//This line prints out the text of the email
            if (strlen(msg.text.charSet))
                MailClient.printf("Text Message Charset: %s\n", msg.text.charSet);
            if (strlen(msg.text.transfer_encoding))
                MailClient.printf("Text Message Transfer Encoding: %s\n", msg.text.transfer_encoding);
            if (strlen(msg.html.content))
                MailClient.printf("HTML Message: %s\n", msg.html.content);
            if (strlen(msg.html.charSet))
                MailClient.printf("HTML Message Charset: %s\n", msg.html.charSet);
            if (strlen(msg.html.transfer_encoding))
                MailClient.printf("HTML Message Transfer Encoding: %s\n\n", msg.html.transfer_encoding);

            if (msg.rfc822.size() > 0)
            {
                MailClient.printf("\r\nRFC822 Messages: %d message(s)\n****************************\n", msg.rfc822.size());
                printMessages(msg.rfc822, headerOnly);
            }

            if (msg.attachments.size() > 0)
                printAttacements(msg.attachments);
        }

        Serial.println();
        if (msg.text.content == "YES")
        {
          Serial.println("it found YES");
        }
        else
        Serial.println("it found NO");
    }
}

Have you tried printing msg.text.content to the serial monitor to see what it contains?
[Edit, I can see something to print it in the main part of the code, but I've not checked to see if that line is enabled]

If M$ is not envolved: Just scan the message body for the keyword.
if M$ is involved: You'll need to handle malformed multipart messages - or just do brute force base64 decoding of all parts in the mail body.

In any case you'll need some kind of authentication/signing/encryption to be on the safe side.

You should give an overview about your project.
It might well be that there is a much easier way than using Email to obtain the functionality that you want to have.

Hi Dave,

This line prints out the text of the email:

MailClient.printf("Text Message: %s\n", msg.text.content);//This line prints out the text of the email

...but I don't fully understand the syntax of that line. All I know is that it results in a print out of the text of the email (which is simply YES). It looks like this in the serial monitor:

Text Message: YES

The printf() function prints the text passed to it in quotation marks but can also print the value of variables passed to it and control characters

The %s paramenter indicates that it should print a C style string (NOT a String) at that point and the \n prints a newline character. The data to be printed, in this case msg.text.content, is passed to the function after the text and formatting commands

NOTE that the function is printing a C style string (lowercase s), ie a zero terminated array of chars, not a String (uppercase S)

You cannot test the contents of a C style string using the normal equality operator (==) but need to use the strcmp() function instead

Try

if (strcmp(msg.text.content, "YES") == 0)

instead

1 Like

Hi Stefan,

The project is a mains power supply monitor; it has a trickle charged battery which powers the ESP32, the house router and the line terminating unit (in the UK, these are transitioning to being locally powered devices as the telephone network transitions to an IP based service, so I have to run that off the battery as well to ensure we still have broadband during a mains power failure). The system simply detects when the mains is lost (by monitoring the state of a changeover contact on a mains powered relay), and then sends an email alarm to alert to the mains failure. It does a bit of re-checking before sending the email, to avoid alarms in the case where the mains goes off, then comes back on again a few seconds later, but that's about it. This part is easy and I have it working.

I now want to extend the system so that I can send an email to the system to interrogate the state of the mains supply (i.e. is it ON or OFF); I'd like to send a mail to it, the system will check the mains supply status, and then respond with an email giving the state of the mains.

So my challenges are: ideally, I want the received email to trigger the system to do a check and respond, and hence I need to read the text (or Subject line) of the incoming e-mail - or do I? I guess the very existence of a new email could be taken as the trigger to do the mains check. But I know that other emails get sent to that email address from Google, for example, so I might get random responses that I hadn't initiated! But that's not a big deal....

Any alternative approaches gratefully received!

Hi UKHeliBob,

Thanks for your response - it worked.

I changed the code slightly to this:

if (strcmp(msg.text.content, "YES") == 0)
        {
          Serial.println("it found YES");
        }
        else
        if (strcmp(msg.text.content, "NO") == 0)
        {
          Serial.println("it found NO");
        }
        else {
        Serial.println("it found neither YES nor NO");
        }

I sent a new mail with YES in the text, and it printed "it found YES" and then sent a new mail with NO in the text, restarted the ESP32, and it printed "it found NO". I then sent a third mail with garbage in the text, restarted the ESP32, and it printed "neither YES nor NO".

I now need to work out how to get the system to act when a new mail comes in....

One point on the (strcmp(msg.text.content, "NO") == 0) that I'm confused by is the "==0" and not "==1". I was expecting that the strcmp function to return a 1 for TRUE rather than a zero.

But you have solved my problem of finding the text match, which is progress, so thank you!

Apologies for my query on strcmp(). I now understand how it works and why it will return zero if the texts are identical.

I know what you mean but strcmp() is a little more subtle than it seems at first sight as it can return one of 3 different values which would not be possible if it returned a boolean. Whether you think that is useful is up to you but we are stuck with it

See strcmp() in C - GeeksforGeeks