Learn the basics of sending custom email notifications using Netlify functions and your email service of choice.
While Netlify supports email notifications around triggers and features within its system (e.g. deploy events, form submissions), you may want to add custom email notification triggered by actions from users on your site.
Notifications sound tricky, though, don't they?
Well, they don't have to be. We can use Netlify Functions, along with some email-sending service, to make that process a walk in the park.
To keep this example as simple as possible, we're going to use Nodemailer with Ethereal as our email sending service. That means we will have to configure very little, but the emails will be caught, not sent.
Let's dive into a quick example! (You can view a full version of the example code here at any time.)
Before we get started, you'll want a new project. Create a directory for your project. My first steps in a new project are usually these:
$ npm init -y
$ echo 'node_modules' >> .gitignore
Then you can install the only dependency we need, Nodemailer:
$ npm install nodemailer
Let's add a Node-based Netlify function that will handle sending our email message.
The function will expect a stringified JSON object as the body, containing two key-value pairs:
email
: The email address to use to send the message.body
: The message to use as the body of the email.Place the following code in netlify/functions/send-email.js
.
netlify/functions
is the default location for functions. If you have overridden this value for you site, be sure to place the file in the appropriate location.
Also note that we're having Nodemailer make use of Ethereal, which is a mail-catching service. That means none of the messages are going to actually be delivered. I'll talk a little more about this when we get to Next Steps at the end of this post.
netlify/functions/send-email.js
const nodemailer = require("nodemailer");
exports.handler = async function (event, context, callback) {
// Parse the JSON text received.
const body = JSON.parse(event.body);
// Build an HTML string to represent the body of the email to be sent.
const html = `<div style="margin: 20px auto;">${body.body}</div>`;
// Generate test SMTP service account from ethereal.email. Only needed if you
// don't have a real mail account for testing
let testAccount = await nodemailer.createTestAccount();
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: testAccount.user, // generated ethereal user
pass: testAccount.pass, // generated ethereal password
},
});
try {
// send mail with defined transport object
let info = await transporter.sendMail({
from: '"☁️ The Cloud ☁️" <thecloud@example.com>',
to: body.email,
subject: "New Form Submission",
text: body.body,
html: html,
});
// Log the result
console.log(info);
callback(null, { statusCode: 200, body: JSON.stringify(info) });
} catch (error) {
// Catch and log error.
callback(error);
}
};
This will log the result to the console, regardless of whether it is successful or not (callback(error)
will print feedback), so you can have an idea of what's going on.
Next, let's build a simple HTML page that gives you the ability to set the email
and body
fields. Then we'll add just a little JavaScript to make it all work.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Send Email Notifications with Netlify Functions</title>
</head>
<body>
<form onsubmit="submitForm(event)">
<div style="margin-bottom: 1rem">
<label for="email">Email Address</label>
<input
id="email"
type="text"
name="email"
placeholder="Where should I send the message?"
required
/>
</div>
<div style="margin-bottom: 1rem">
<label for="body">Message</label>
<textarea id="body" name="body" cols="30" rows="10" required></textarea>
</div>
<input type="submit" value="Send email" />
</form>
<script>
function submitForm(event) {
// Stop the browser's default behavior.
event.preventDefault();
// Retrieve data from the form.
const formData = new FormData(event.target);
const request = new XMLHttpRequest();
// Convert data to JSON object.
var jsonData = {};
formData.forEach((value, key) => (jsonData[key] = value));
// Send the data to the Netlify function.
request.open("POST", "/.netlify/functions/send-email");
request.send(JSON.stringify(jsonData));
// Clear the form.
alert("Email request submitted!");
event.target.reset();
}
</script>
</body>
</html>
It's not good practice to use an onsubmit
attribute on a form element to call a global function when submitting a message. This is just a very simple example for demonstration purposes.
If you're not getting the email messages in the inbox you specified, it's because we're not actually delivering them!
WTF?
Yes. Ethereal, which we're using as our email server, is a mail-catching service, which means that it catches the mail requests and lets you read them, but it doesn't actually send them.
If you want to see the messages being caught, then instead of creating a test account in the function, go to Ethereal and click Create Ethereal Account. Then plug the username and password in. You can then visit the inbox for that account and see everything that was caught.
Being that this is just the beginning of something that you'd actually put into practice, here are some ideas on where you could take it from here to get it ready for production:
No matter where you go with it, I'd love to learn more about your approach or any questions you have. Let's chat.