Ingenious logo

Build a custom server that handles your SPA and your API

A few days ago we added a new Invite people by email feature, to an app we’re developing internally at Ingenious. The app consists of a board with a collaborative text editor and some cards that complement the behavioral insight approach used in product design consulting. Tech-wise the app is built with React, bootstrapped with create-react-app, Firebase as the backend and hosted on Heroku.

We needed to invite people as Editors of the board by sending an email with the board URL. To keep things simple, we first tried EmailJS, a service that allows sending emails without focusing on any server code. EmailJS would have been an excellent choice but we needed a bit more of control on how the email was generated, for example, it is impossible to add custom fonts to the HTML version of the email.

We choose, as the path of least disruption, to set up a small Express (Node) backend to serve our React app and publish an endpoint that takes care of sending the email. I’ve found several tutorials on the topic, but most of them were outdated or incomplete, so wanted to share a walk through of our solution in case it benefits you as well.

If you like to deep dive into the code, check the demo link at the end.

The first task is to transform our client only React project into a full express app with the following steps:

  • Remove create-react-app-buildpack from the app and setup the default heroku/nodejs buildpack.
  • Move the root React app files into a subdirectory, e.g. react-ui.
  • Copy server/index.js, package.json, and .gitignore files in the root of the project from the buildpack example repo.
  • Commit and push to the Heroku origin.

To keep the React SPA up and working we need to serve all its assets using the static express middleware. Afterward, we can process any request to perform custom actions, such as sending an email, using the common get(…), post(…) express handlers. Finally, we can re-route any non API request to the SPA entry point, that way the client side routing continues to work as expected. Check the code below:

// server/index.js
app.use(express.json()); /* Parse json in request */
/* Priority serve any static files */
app.use(express.static(path.resolve(__dirname, "../react-ui/build")));
app.get("/api/send_email", function(req, res) {
res.set("Content-Type", "application/json");
/* Send email here */
res.send('{"message":"Email sent."}');
/* All remaining requests return the React app, so it can handle routing. */
app.get("*", function(request, response) {
response.sendFile(path.resolve(__dirname, "../react-ui/build", "index.html"));

The key here is to run npm run buildin the React project which copies all the assets: JS, CSS, images, etc. to the build folder. The command also generates an index.html file which can be returned by our web server. This process can be triggered by adding a post-build script in the express package.json file:

"scripts": {
"start": "node server",
"heroku-postbuild": "cd react-ui/ && npm install && npm install --only=dev --no-shrinkwrap && npm run build"

To generate the email we can rely on the email-templates package. It allows us to obtain .html and .text versions of the content and later send the email with a 3rd party provider. We’ll show how to work with the Mailjet API but, any other vendor will do.

var path = require("path");
var templatesDir = path.resolve(__dirname, "views/mailer");
var Email = require("email-templates");
const mailjet = require("node-mailjet").connect(
const sendEmail = (messageInfo, text, html) => {
return"send", { version: "v3.1" }).request({
Messages: [
From: { Email: messageInfo.fromEmail, Name: messageInfo.fromName },
To: [ { Email: } ],
Subject: messageInfo.subject,
TextPart: text,
HTMLPart: html
exports.sendOne = function(templateName, messageInfo, locals) {
const email = new Email({
views: { root: templatesDir, options: { extension: "ejs" } }
return Promise.all([
email.render(`${templateName}/html`, locals),
email.render(`${templateName}/text`, locals)
.then(([html, text]) => {
return sendEmail(messageInfo, text, html);

Finally, we modify the express route handler to obtain the email address, alongside the user-provided content used in the *.*ejs template and send the email.

// server/index.js
const mailer = require("./mailer"); // In the top of the file
/* ... */
app.get("/api/send_email", function(req, res) {
res.set("Content-Type", "application/json");
const locals = { userName: req.body.userName };
const messageInfo = {
email:, fromEmail: "",
fromName: "Star Wars", subject: "Checkout this awesome droids"
mailer.sendOne("droids", messageInfo, locals);
res.send('{"message":"Email sent."}');

Creating HTML emails that work well in many different email clients is a daunting task. If you ever need to build a custom email take a look into MJML, a component-based framework that generates responsive emails templates. It ships with some excellent templates and an easy to grasp documentation that gets the task done.

You can check a full demo repository here.

Weaving hand Schedule a call