Testing Emails with PHP, Gmail, and IMAP
I already discussed my open source project, Courier, and writing integration tests for SMTP emails using MailHog. Most of the courier implementations do not use SMTP, though. Even more importantly, I found that when testing email delivery through services like SparkPost and SendGrid there are a lot of edge cases that should be known and understood. For example, there is an issue sending emails to CC recipients using templates with SparkPost. The only way to consistently ensure these errors are handled as the package is maintained is through integration tests.
A full implementation of these integration tests can be seen here
With this, I decided to write integration tests for each courier. The basic structure of a test I wanted was
- Build a client
- Create an email
- Send the email to a known inbox
- Wait for the email to arrive in the inbox
- Parse the found email
- Ensure the expected values exist on the delivered email
Then I would repeat the process with a templated email (where the markup for the email lived on the service’s servers).
I decided I would send the emails to an existing Gmail account, and pull the emails out of that inbox using the functions from the PHP IMAP extension.
My first goal was to use a single Gmail account, just to make maintenence of the account easier. However, I still needed
to be able to receive emails into this account as the to, CC, and BCC recipients. For this, I set up three inboxes on
my Gmail account
Courier/BCC. From here, I used a pattern of adding
+bcc to the recipient addresses in the email, such as
firstname.lastname@example.org, and I created filters to move emails
with the each suffix into it’s respective inbox in Gmail.
Next, I needed to access the inboxes using PHP’s IMAP functions. For this, I created an application password on my Gmail account. Once I did this, I accessed the emails using code similar to the below.
Security note: For this to work consistently on a CI server, your Gmail account should not have 2FA on it
Finally, I needed a consistent way to parse the MIME email content found by the IMAP function. I found a great package
called (Mail MIME Parser)[https://mail-mime-parser.org/]. And so instead of just returning the body of the email as a
string, my testing function would initialize a parser and return a
The Final Product
Putting all of this together, I was able to create a base test class for my integration testing that allowed me to easily
pull emails from the respective inbox based on the subject of the email as a parsed
for comparison to expected values.