/Email Testing

How to test email with Cypress

What is Cypress?

Cypress is an end-to-end testing framework that has won over developers and testers for its ease of use and flexibility. To use Cypress for your browser test automation, you’ll need to write your tests in JavaScript. Once your code is ready, you’ll be able to repeatedly run test cases using a real browser.

What makes Cypress unique is its batteries-included approach: all the tools required for end-to-end testing are already part of the Cypress framework, and there’s no need for anything extra. Fewer tools to install means faster setup and more time to write tests. Because Cypress’s components are well-integrated, its tests are usually more stable than end-to-end tests using other frameworks.

What is Mailosaur?

Mailosaur is a testing suite that lets you automate email testing with Cypress and other testing frameworks. This is important for websites and web apps that send email (password resets, account setup, marketing emails, etc.). Mailosaur also lets you test SMS messages with Cypress too!

If you don’t already have one, you’ll need to create a free trial account. Once you’re in, just familiarize yourself with sending email into Mailosaur and you’ll be ready to start testing!

Install the Mailosaur plugin

Mailosaur provides an official set of custom commands that let you easily automate email testing in Cypress.

Extend Cypress by installing Mailosaur’s custom commands package: cypress-mailosaur.

1. Install the plugin via npm

npm install cypress-mailosaur

2. Import Mailosaur into your Cypress project

Once installed, open cypress/support/e2e.js in your project folder, and add the following line of code to it:

import "cypress-mailosaur";

Note: In older versions of Cypress, the file may be called cypress/support/index.js.

3. Configure your API key

In order to connect to Mailosaur, you need to add an API key to your tests. You access your API key via in the account settings area.

Add your Mailosaur API key to cypress.config.js, in your project folder:

module.exports = defineConfig({
  env: {
    MAILOSAUR_API_KEY: "your-key-here",
  },

  // ...
});

Note: We strongly recommend against storing API keys in source control. Therefore you can also set your API key using the environment variable CYPRESS_MAILOSAUR_API_KEY.

Basic usage

The mailosaurGetMessage command automatically waits for the first email to arrive that matches the search criteria you provide.

cy.mailosaurGetMessage("SERVER_ID", {
  sentTo: "anything@SERVER_ID.mailosaur.net",
}).then((email) => {
  cy.log(email.subject);
});

To learn about Server IDs, and what to replace SERVER_ID with, see sending email to Mailosaur.

This example searches for the email address a message was sent to, but you can also search using any of this criteria too:

PARAMETER DESCRIPTION
sentTo The full email address to which the target message was sent.
sentFrom The full email address from which the target message was sent.
subject Find emails where the subject line contains this text.
body Finds messages where the message body contains this text.

The mailosaurGetMessage command returns the message object, which contains everything you need to perform in-depth automated testing.

Increasing the wait time

By default, the mailosaurGetMessage command will wait for 10 seconds for a matching message to arrive. You can increase this wait time by using the timeout option:

cy.mailosaurGetMessage("SERVER_ID", {
  sentTo: "anything@SERVER_ID.mailosaur.net",
}, {
  timeout: 20000, // 20 seconds (in milliseconds)
}).then((email) => {
  cy.log(email.subject);
});

Searching for older messages

By default, the mailosaurGetMessage command will only find messages received in the last hour. You can change this by setting the receivedAfter option.

const testStart = new Date();

// TODO Do something here to send an email into your account

// Find any email received after `testStart` was set
cy.mailosaurGetMessage("SERVER_ID", {
  sentTo: "anything@SERVER_ID.mailosaur.net",
}, {
  receivedAfter: testStart,
}).then((email) => {
  cy.log(email.subject);
});

Test email addresses

Because Mailosaur email addresses use a wildcard pattern, all plans give you access to unlimited test email addresses. Any address ending @YOUR_SERVER_ID.mailosaur.net will work.

You can use your own logic to make up an email address, or use the mailosaurGenerateEmailAddress helper method to generate a random string for you:

cy.mailosaurGenerateEmailAddress("SERVER_ID").then((emailAddress) => {
  cy.log(emailAddress); // "bgwqj@SERVER_ID.mailosaur.net"
});

Testing basic properties

Once you have fetched an email (e.g. using mailosaurGetMessage), you can easily test the properties of that email:

.then((email) => {
  // Test the email subject line
  expect(email.subject).to.equal("Password reset");

  // Test sender information
  expect(email.from[0].name).to.equal("Support");
  expect(email.from[0].email).to.equal("noreply@acme.com");

  // Test recipient information
  expect(email.to[0].name).to.equal("John Smith");
  expect(email.to[0].email).to.equal("john.smith@example.com");
});

Testing carbon-copy recipients

.then((email) => {
  // Carbon copy (CC) recipients
  expect(email.cc[0].name).to.equal("Jane Smith");
  expect(email.cc[0].email).to.equal("jane.smith@example.com");

  // Blind carbon copy (BCC) recipients
  expect(email.bcc[0].name).to.equal("Jill Smith");
  expect(email.bcc[0].email).to.equal("jill.smith@example.com");
});

Testing email contents

Most emails will contain separate HTML and plain text content. You can see below how to test either content type:

Plain text content

.then((email) => {
  // Check that the email contains some text
  expect(email.text.body).to.contain("Expected text");
});

HTML content

When testing HTML content, you can perform basic contains or equals assertions in the same way as with plain text content:

.then((email) => {
  // Check that raw HTML contains some expected content
  expect(email.html.body).to.contain("<span>Hello world</span>");
});

In addition, if you want to perform lookups using CSS selectors, etc. you can use jsdom alongside Mailosaur.

You can install jsdom using npm:

npm install jsdom

Import the library at the start of your test file:

const jsdom = require("jsdom");

Now perform HTML operations like this:

.then((email) => {
  // Use a CSS selector to find the target element
  const dom = new JSDOM(email.html.body);
  const el = dom.window.document.querySelector('.customer-ref');

  // Check the contents of the target element
  expect(el.textContent).to.equal('XYZ123');
});

Testing links

Any links that are found in the content of your email are instantly available via the html.links or text.links arrays, depending on which content the link was found in:

.then((email) => {
  cy.log(`${email.html.links.length} links found in HTML content`);
  cy.log(`${email.text.links.length} links found in plain text content`);

  // Check the first link found in HTML content
  expect(email.html.links[0].href).to.equal('https://google.com');
  expect(email.html.links[0].text).to.equal('Google');
});

Navigating to a link

You can navigate to any link using the Cypress command `cy.visit`:

.then((email) => {
  // If the link is on a different domain you will also need to use `cy.origin`.
  // See https://docs.cypress.io/api/commands/visit#Visiting-cross-origin-sites
  return cy.visit(email.html.links[0].href);
})
.then(() => {
  // Do something on the new page
});

Clicking a link

If you just need to simulate a link being clicked and do not need to do anything on the target page, you can do this with https.

Import https at the top of your test file:

const https = require("https");

Then use this in your test to perform a 'click':

.then((email) => {
  // Make an HTTP call to simulate someone clicking a link
  https.get(email.html.links[0].href, r => cy.log(r.statusCode)) // 200
})

Testing verification codes

Codes are automatically extracted from the content of your email. Just like links, codes are made available via the html.codes or text.codes arrays, depending on which content the code was found in:

.then((email) => {
  cy.log(`${email.html.codes.length} codes found in HTML content`);
  cy.log(`${email.text.codes.length} codes found in plain text content`);

  // Check the first code found in HTML content
  expect(email.html.codes[0].value).to.equal('432123');
});

Testing attachments

Any email attachments are made available via the attachments array:

.then((email) => {
  cy.log(`${email.attachments.length} attachments found`);

  // Get the first attachment
  const attachment = email.attachments[0];

  // Check attachment attributes
  expect(attachment.fileName).to.equal('example.pdf');
  expect(attachment.contentType).to.equal('application/pdf');

  // Check the file size (in bytes)
  expect(attachment.length).to.equal(4028);
});

Save an attachment to disk

You can download and save attachments using the mailosaurDownloadAttachment and writeFile commands:

.then((email) => {
  cy.log(`${email.attachments.length} attachments found`);

  return cy.mailosaurDownloadAttachment(email.attachments[0].id);
})
.then((file) => {
  // Save file to disk
  cy.writeFile('some-file.pdf', file, 'binary');
});

Encoding attachments

In some scenarios you may need a base64-encoded copy of an attachment. You can achieve this using toString('base64'):

.then((email) => {
  cy.log(`${email.attachments.length} attachments found`);

  return cy.mailosaurDownloadAttachment(email.attachments[0].id);
})
.then((file) => {
  const base64 = file.toString('base64');
});

Testing images and web beacons

Any images found within the HTML content of an email are made available via the html.images array:

.then((email) => {
  cy.log(`${email.html.images.length} images found`);

  // Get the first attachment
  const image = email.html.images[0];

  // Check image attributes
  expect(image.src).to.equal('https://example.com/balloon.png');
  expect(image.alt).to.equal('A green hot air balloon in the sky');
});

To test whether an image is accessible online, or that a web beacon is working as expected, you can simply perform an HTTP request, using https.

Import https at the top of your test file:

const https = require("https");

Then use this in your test to fetch the image:

.then((email) => {
  cy.log(`${email.html.images.length} images found`);

  // Get the first attachment
  const image = email.html.images[0];

  // Fetch the image
  https.get(image.src, r => cy.log(r.statusCode)) // 200
})

Testing deliverability

The mailosaurGetSpamAnalysis command runs a SpamAssassin test against an email. This will return an overall SpamAssassin score (see understand your SpamAssassin score).

The lower the score the better and, in most cases, a score higher than 5.0 will result in an email being seen as spam.

.then((email) => {
  return cy.mailosaurGetSpamAnalysis(email.id);
})
.then((result) => {
  // Overall SpamAssassin score. In most cases a score of
  expect(result.score).to.be.lessThan(3);

  result.spamFilterResults.spamAssassin.forEach(r => {
    cy.log(r.rule);
    cy.log(r.description);
    cy.log(r.score);
  });
})

List all messages

Recommendation: We strongly recommend using the `mailosaurGetMessage` command for most use cases. This command has been purposely optimised for the most common automated testing scenarios.

If you simply need to list the current emails in an inbox, you can use mailosaurListMessages:

cy.mailosaurListMessages("SERVER_ID")
  .then((result) => {
    cy.log(`${result.items.length} messages in inbox`);

    const email = result.items[0];

    expect(email.subject).to.equal("Password reset");

    // Note: List results only contain summary information. To get
    // the HTML/text content of an email, use `mailosaurGetMessageById`
    return cy.mailosaurGetMessageById(email.id);
  })
  .then((email) => {
    cy.log(email.html.body);
  });

Alternative search for messages

Recommendation: We strongly recommend using the `mailosaurGetMessage` command for most use cases. This command automatically waits for a matching result, and also returns the whole message object in the result, rather than just a summary.

Using the mailosaurSearchMessages command:

cy.mailosaurSearchMessages("SERVER_ID", {
  sentTo: "anything@SERVER_ID.mailosaur.net",
})
  .then((result) => {
    cy.log(`${result.items.length} matching messages found`);

    // Get the first result
    const email = result.items[0];

    expect(email.subject).to.equal("Password reset");

    // Note: Results only contain summary information. To get
    // the HTML/text content of an email, use `mailosaurGetMessageById`
    return cy.mailosaurGetMessageById(email.id);
  })
  .then((email) => {
    cy.log(email.html.body);
  });

Wait for matches to arrive

By default the mailosaurSearchMessages command will only return current matches. By setting the timeout option, the command will automatically wait for a match:

cy.mailosaurSearchMessages("SERVER_ID", {
  sentTo: "anything@SERVER_ID.mailosaur.net",
}, {
  timeout: 10000, // Wait 10 seconds (in milliseconds) for a match
});

Testing that no matching messages are found

By default, an error will be thrown if no matches are found in time. However, if you are expecting no messages to arrive in a certain period of time, then simply set errorOnTimeout: false to prevent an exception being thrown:

cy.mailosaurSearchMessages("SERVER_ID", {
  sentTo: "anything@SERVER_ID.mailosaur.net",
}, {
  timeout: 10000,
  errorOnTimeout: false,
});

Deleting messages

Delete all messages

To permanently delete all messages held in a specific server/inbox, use the mailosaurDeleteAllMessages command. This also deletes any attachments related to each message. This operation cannot be undone.

cy.mailosaurDeleteAllMessages("SERVER_ID");

Deleting an individual message

To permanently delete a single message, use mailosaurDeleteMessage. This also deletes any attachments related to the message. This operation cannot be undone.

cy.mailosaurDeleteMessage("MESSAGE_ID");

Download an email (.EML)

If you need to download a copy of an email, you can do this using the mailosaurDownloadMessage command.

This will download a .EML format file, allowing you to save an email offline and outside of Mailosaur or any other email software.

.then((email) => {
  return cy.mailosaurDownloadMessage(email.id);
})
.then((file) => {
  // Save file to disk
  cy.writeFile('email.eml', file, 'binary');
});

Replying to an email

If your product is capable of handling email replies from your customers, you can use Mailosaur’s reply feature to simulate such a scenario.

When you reply, the email is sent back to the email address it was originally sent to Mailosaur from. Before you can reply, you must first add this address as a verified external email address.

cy.mailosaurReplyToMessage("MESSAGE_ID", {
  text: "FYI",
});

The options available to use with the mailosaurReplyToMessage command are:

PARAMETER DESCRIPTION
text Any additional text content to reply to the email with.
html Any additional HTML content to reply to the email with.
subject Optionally override the default subject line.
attachments Optional attachments (see 'inline attachments' below).

Include attachments

You can include attachments in emails sent via the API, by including an array of base64-encoded attachment objects:

const attachments = [
  {
    fileName: "cat.png",
    contentType: "image/png",
    content: "{BASE64_ENCODED_FILE}",
  },
];

cy.mailosaurReplyToMessage("MESSAGE_ID", {
  to: "verified-address@example.com",
  subject: "Email from Mailosaur",
  html: "<p>Hello world.</p>",
  attachments: attachments,
});

Forwarding an email

Teams often need to forward emails onto an email address outside of Mailosaur. For example, to send messages to colleagues for further review/analysis, or to test email handling functionality within an product.

You can forward messages from your Mailosaur account to external email addresses either one-by-one, or via the creation of automated forwarding rules.

Before you can forward messages, you must set up a verified external email address, so that you can send email to it.

cy.mailosaurForwardMessage("MESSAGE_ID", {
  to: "verified-address@example.com",
  text: "Hello world",
});

The options available to use with the mailosaurForwardMessage command are:

PARAMETER DESCRIPTION
to The email address to which the email will be sent. Must be a verified email address.
text Any additional text content to forward the email with.
html Any additional HTML content to forward the email with.
subject Optionally override the default subject line.

Sending an email

If your product is capable of handling inbound emails, you can use Mailosaur’s sending feature to trigger this functionality in your product.

The mailosaurCreateMessage command creates and and sends an email to a verified external email address:

cy.mailosaurCreateMessage("SERVER_ID", {
  to: "verified-address@example.com",
  send: true,
  subject: "Request",
  text: "Please can you give us a call back?",
});

The options available to use with the mailosaurCreateMessage command are:

PARAMETER DESCRIPTION
send This must be set to true if you are sending an outbound email.
to The email address to which the email will be sent. Must be a verified email address.
subject The email subject line.
text Any additional text content to forward the email with.
html Any additional HTML content to forward the email with.
attachments Optional attachments (see 'inline attachments' below).

Include attachments

You can include attachments in emails sent via the API, by including an array of base64-encoded attachment objects:

const attachments = [
  {
    fileName: "cat.png",
    contentType: "image/png",
    content: "{BASE64_ENCODED_FILE}",
  },
];

cy.mailosaurCreateMessage("SERVER_ID", {
  to: "verified-address@example.com",
  send: true,
  subject: "Email from Mailosaur",
  html: "<p>Hello world.</p>",
  attachments: attachments,
});

Working with servers

List servers

Return a list of your servers/inboxes. Servers are returned sorted in alphabetical order:

cy.mailosaurListServers().then((result) => {
  cy.log("You have a server called:", result.items[0].name);
});

Create a new inbox

Create a new server (inbox). Only the name property is required to create a new server via the API:

cy.mailosaurCreateServer({
  name: "My email tests",
});

Retrieve a server

Retrieve the detail for a single server. Simply supply the unique identifier for the required server:

cy.mailosaurGetServer("SERVER_ID").then((server) => {
  cy.log(server.name);
});

Retrieve the SMTP/POP3 password for a server

Retrieve the password, for use with SMTP and POP3, for a single server. Simply supply the unique identifier for the required server:

cy.mailosaurGetServerPassword("SERVER_ID").then((password) => {
  cy.log(password);
});

Update an server

Updates a single server/inbox:

retrievedServer.name = "Updated server name";

cy.mailosaurUpdateServer(retrievedServer);

Delete an inbox

Permanently delete a server. Also deletes all messages and associated attachments within the server. This operation cannot be undone:

cy.mailosaurDeleteServer("SERVER_ID");

See also