Email delivery testing is an essential part of ensuring your application sends emails correctly and efficiently. With Mailsac, you can take advantage of its powerful REST API to simplify your email testing process. In this blog post, we’ll show you how to use Cypress, a popular end-to-end testing framework, in combination with Mailsac for seamless email delivery testing. We’ll cover setting up the Cypress environment, running tests, and provide plenty of code samples to get you started.
Setting up the Cypress Environment
First, you’ll need to have Node.js installed on your computer. Once that’s done, follow these steps to set up Cypress:
1. Create a new directory for your project and navigate to it in the terminal. b. Run npm init to create a package.json file. c. Install Cypress by running npm install cypress. d. Add a script to your package.json file to run Cypress:
"scripts": {
"cypress:open": "cypress open"
}
2. Configuring Cypress and Mailsac API
Next, create a cypress.json file in your project’s root directory. This file will store your Mailsac API key and other configuration options:
Replace your_mailsac_api_key with your actual Mailsac API key.
3. Writing Your First Cypress Test
Now, let’s create a test file in the cypress/integration folder. Name it email_delivery_spec.js. In this file, we’ll write a test that sends an email to a random Mailsac address and then checks whether the email was received.
// cypress/integration/email_delivery_spec.js
describe("Email Delivery Test", () => {
it("sends an email and verifies its receipt", async () => {
const randomEmail = `test-${Math.random().toString(36).substring(2)}@mailsac.com`;
const testSubject = `Cypress Email Delivery Test ${Math.random().toString(36).substring(2)}`;
const testBody = "This is a test email sent using Cypress and Mailsac.";
// Send an email using your application's email sending method
// ...
// TOOD: integrate your app here!
// Function to poll Mailsac API for received messages. It will be called
// recursively.
const checkEmail = async () => {
let response = await cy.request({
method: "GET",
url: `https://mailsac.com/api/addresses/${randomEmail}/messages`,
headers: {
// Get a mailsac api key at: mailsac.com/api-keys
"Mailsac-Key": Cypress.env("mailsac_api_key")
}
});
const messages = response.body;
const message = messages.find(msg => msg.subject === testSubject);
if (!message) {
return cy.wait(1000).then(() => {
checkEmail();
});
}
expect(message.from[0].address).to.equal("[email protected]");
expect(message.inbox).to.equal(randomEmail);
// Check email content for testBody text
const textResponse = await cy.request(`https://mailsac.com/api/text/${randomEmail}/${msg._id}`);
expect(textResponse.body).to.contain(testBody);
// Clean up by deleting the received messages. This could also be done in an afterEach block.
await Promise.all(messages.map((msg) =>
cy.request({
method: "DELETE",
url: `https://mailsac.com/api/addresses/${randomEmail}/messages/${msg._id}`,
headers: {
"Mailsac-Key": Cypress.env("mailsac_api_key")
}
})
));
};
// Start polling for received messages
await checkEmail();
}
);
});
Replace [email protected] with your application’s sender email address.
This will open the Cypress Test Runner, and you’ll see your email_delivery_spec.js test listed. Click on the test to run it.
Developing an application that sends emails is straightforward but not without its risks. Ensuring deliverability but not actually having any of those emails land inside real inboxes is a top concern for any developer. Which leads to questions like: “How do you test your application’s outbound email capabilities?” or “How do I manage email testing for free?”
Enter email capture services. While the term “email capture service” tends to focus on the marketing aspects (capturing information from your calls to action, ensuring emails don’t get caught in spam, etc) they also include SMTP deliverability. Mailsac offers an email capture service that addresses the deliverability aspect, specifically not delivering any email to its intended recipient. Effectively a “black hole” where no email should escape to the outside world.
In this post, we’ll walk through a sample application in Next.js that will generate emails and have those emails captured by Mailsac’s email sandboxing service.
Do I Really Need To Do Email Testing?
Some frameworks do come with email previewing capabilities like Rails’ ActionMailer. Said frameworks don’t actually attempt to send anything but instead preview the email on your machine. We recommend real testing during the development and quality assurance phase by using an external SMTP server to mimic the application’s behavior in production.
Testing that capability has to be done safely unless you want to land on Twitter’s trending page for accidentally sending customers an integration test email.
Test Email Sending With A Next.js Application
For the rest of this guide, we’ll focus on wiring up a simple application that will allow users to send an email when a button is pushed from a UI. We’ll then demonstrate the capture of those emails in our development environment.
The components we’ll use are:
Next.js with a React frontend and API Routes backend
nodemailer package for routing email on the backend
While the focus of this guide isn’t a line-by-line walkthrough of the sample code, we’ll focus on the key aspects of the application that mainly involve emailing capabilities.
The application source can be found in our git repository.
1. Application setup
Let’s start by creating a quick next app with tailwind support:
mailsac % npx create-next-app
…
Success! Created nodejs-send-email at /Users/mailsac/code/nodejs-send-email
cd nodejs-send-email
npm install -D tailwindcss postcss autoprefixer @tailwindcss/forms
npm install @headlessui/react@latest @heroicons/react
npx tailwindcss init -p
The second line brings in a component that takes in a message and formats it as a pop-up notification. The useEffect() method sends your email recipient and body input to the backend, which will forward that data to Mailsac’s servers.
const nodemailer = require("nodemailer");
export default async function handler(req, res) {
let emailEnvelope = JSON.parse(req.body)
if (
req.method === 'POST'
&& typeof(emailEnvelope.to) !== 'undefined'
&& emailEnvelope.to !== ''
){
const mailsaUserName = process.env.MAILSAC_USERNAME
const mailsacAPIKey = process.env.MAILSAC_API_KEY
const transporter = nodemailer.createTransport({
host: 'capture.mailsac.com',
port: 5587,
// will use TLS by upgrading later in the connection with STARTTLS
secure: false,
auth: {
user: mailsaUserName,
pass: mailsacAPIKey
}
})
try {
const results = await transporter.sendMail({
from: '"Sample App" [email protected]',
to: emailEnvelope.to,
subject: 'Sample App Send',
text: emailEnvelope.body
})
res.status(200).json(
{
message: "You should now see an email in Mailsac's capture service",
response: results.data
}
)
} catch (error){
console.log(`ERROR ${error}`)
res.status(500).json({ message: `${error.response}`, response: error })
}
} else {
return res.status(200).json({message: "No data"});
}
}
In the highlighted line, we’re ensuring the useEffect() hook gets called with input data before we allow the rest of the function to continue. useEffect() gets called a variety of times in the component lifecycle, and this check is to ensure it was initiated by an end user and not as part of the component mounting.
5. Test driving the app
Fire up the application via
npm run dev
Navigate to http://localhost:3000 and type a text message:
Then navigate over to mailsac.com to view the message
6. Capturing other email domains
While that works well as a contrived example, the real value comes when using any arbitrary email in the recipient field:
Capturing emails outside the mailsac.com domain is extremely valuable when switching between different environments. For example, in the demo application example above, the .env environment file could instead look like
const nodemailer = require("nodemailer");
export default async function handler(req, res) {
let emailEnvelope = JSON.parse(req.body)
if (
req.method === 'POST'
&& typeof(emailEnvelope.to) !== 'undefined'
&& emailEnvelope.to !== ''
){
const mailsaUserName = process.env.MAILSAC_USERNAME
const mailsacAPIKey = process.env.MAILSAC_API_KEY
const transporter = nodemailer.createTransport({
host: process.env.MAILSAC_HOST,
port: process.env.MAILSAC_PORT,
// will use TLS by upgrading later in the connection with STARTTLS
secure: false,
auth: {
user: mailsaUserName,
pass: mailsacAPIKey
}
})
try {
const results = await transporter.sendMail({
from: '"Sample App" [email protected]',
to: emailEnvelope.to,
subject: 'Sample App Send',
text: emailEnvelope.body
})
res.status(200).json(
{
message: "You should now see an email in Mailsac's capture service",
response: results.data
}
)
} catch (error){
res.status(500).json({ message: `${error.response}`, response: error })
}
} else {
return res.status(200).json({message: "No data"});
}
}
The above edits would allow you to deploy to a testing or production environment and the only changes required would be in the .env file. Specifically, the SMTP host and authentication settings.
Conclusion
The above guide just scratches the surface of what you can do with our email services. We provide a unified inbox that allows testers to view their bulk email testing in one unified view and custom domains for those who do not have their own domains with zero setup configurations.