Importing Email Templates to Send From NextJS API Route

If you’re having trouble importing or reading files from a NextJS API route, you’re not alone. I recently ran into this issue when trying to send self-managed email templates from a NextJS API route and I want to share the solution with you!

The Problem

In my case I was using Mailgun to send notification and transactional emails but I didn’t want my email templates to live in Mailgun. I wanted version control for my email templates and a better editing experience without having to use Mailgun or another third party service. Immediately I ran into the issue of reading the files in a NextJS API route using fs.readFile(../path/to/email-template.html).

Issues Reading Files at Runtime

When you develop locally, your API endpoint ends up in the .next/server/pages/api directory. This isn’t difficult to read from locally if your directory some-directory and subfile some-file.(txt|json|html) is located at the root of your project:

import fs from 'fs';
import path from 'path';
const someAPIEndpoint = async (req, res) => {
const file = fs.readFile(path.join(__dirname, '..', '..', '..', '..', 'some-directory', 'some-file.(txt|json|html)'));
// ...continue
};

But what about on the server? Since these files are read at runtime, your build might have removed your some-directory directory since none of its files were imported into the project. Even if it weren’t removed, it’s likely the directory location is different than your local, so the path might have to be dynamically set, or you might have to move the some-directory directory around to match your local, either way this is getting pretty hacky… I chose to avoid going this direction, and instead chose to try and import the files directly into the API route.

The Solution

Location of Email Templates in NextJS Project

Where to place your email templates in your NextJS project really comes down to preference. I considered putting them…

  1. in the /public directory, but I didn’t want them available to the world via https://mywebsite.com/emails/some-email-template.html
  2. somewhere in my src directory (see documentation on that setup here) but I didn’t think that made sense since these aren’t files used on the front end

In the end I decided to place them at the root of my project in an emails directory. Your project should look something like this:

emails
|_ ...
|_ some-email-template.html
public
|_ ...
|_ some-asset.png
src
|_ ...
|_ components
|_ pages
|_ ...
|_ api
|_ some-api-route.(js|ts)

Importing Email Templates Into NextJS API Route

I felt this was my best option for having my email templates available in my API routes. In order to import an .html file into a .js or .ts file, we need to use the webpack plugin html-loader (documentation found here).

Firstly, install the package:

npm install --save-dev html-loader

Next, modify your next.config.(js|ts) file:

module.exports = {
...,
future: {
webpack5: true,
},
webpack: (config) => {
config.module.rules.push({
test: /\.html$/,
loader: 'html-loader',
});
return config
},
...,
};

At the time of this article, NextJS does not support webpack 5 out of the box, so we first enable it in the future object. Webpack 5 is required by html-loader. If you don’t want to us webpack 5, I suggest lowering your version of html-loader. Next, we add the plugin to the rules array, specify the test, and define the loader html-loader.

If you’re using typescript, to avoid import errors cannot find module '../../*.html' or its corresponding typee declarations, make sure to create a typings.d.ts file at the root of your project with the following contents:

declare module '*.html' {
const value: any;
export default value;
}

You should now be able to import your email template directly into your NextJS API Route!

src/pages/api/some-email.(js|ts)

import mailgun from 'mailgun-js';
import SomeEmailTemplate from '../../../emails/some-email-template.html';
const mg = mailgun({
apiKey: process.env.MAILGUN_API_KEY,
domain: process.env.MAILGUN_DOMAIN!
});
const someAPIRoute = async (req, res) => {
try {
await mg.messages().send({
from: ‘youremail@email.com’,
to: ["someemail@email.com"],
subject: "This is an email template hosted in our NextJS project!",
html: SomeEmailTemplate
});
} catch (error) {
console.error(‘Error sending email: ‘, error);
}
// ...continue
};

That’s it! Your email template will be sent using Mailgun.

Final Thoughts

Thanks for taking the time to read this, and I hope it saves you time troubleshooting and coming up with a solution on your own! Post your questions in the comments and I’ll get back to you! Thanks for reading!

You can find me on GitHub, LinkedIn, my website, and Twitter!

Full time front-end web developer specializing in Angular. Always trying to learn new things. “There is no growth in the comfort zone…”

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store