tutorial // May 13, 2022

How to Define Templates and Send Email with Joystick

How to set up an SMTP service, prepare an email template using Joystick components, and send an email using the email.send() function in @joystick.js/node.

How to Define Templates and Send Email with Joystick

Getting Started

For this tutorial, we're going to use CheatCode's full-stack JavaScript framework, Joystick. Joystick brings together a front-end UI framework with a Node.js back-end for building apps.

To begin, we'll want to install Joystick via NPM. Make sure you're using Node.js 16+ before installing to ensure compatibility (give this tutorial a read first if you need to learn how to install Node.js or run multiple versions on your computer):

Terminal

npm i -g @joystick.js/cli

This will install Joystick globally on your computer. Once installed, next, let's create a fresh project:

Terminal

joystick create app

After a few seconds, you will see a message logged out to cd into your new project and run joystick start:

Terminal

cd app && joystick start

After this, your app should be running and we're ready to get started.

Configuring SMTP

Before we focus on code, in order to actually send our email, we'll need access to an SMTP provider. There are quite a few options out there. If you have a favorite, feel free to use that, but for this tutorial we're going to recommend Postmark. Postmark is a great SMTP service that offers—in my opinion—the best SMTP product on the market.

If you don't already have an account, head over to their sign up page and create one. Once logged in, Postmark will automatically create a "server" (a server in Postmark is the project related to the app you're sending email for) called "My First Server."

Once logged in, you should see something like this:

UhVnnxDaZUVLA0y8/HkkVeGERfowscaNG.0
The "My First Server" page in Postmark.

From here, you will want to click the "API Tokens" tab just to the right of the highlighted "Message Streams" tab.

If you hover the populated input next to "Server API Tokens," you will be given an option to click and copy the value in the box. Go ahead and do this and then open up the /settings.development.json file at the root of the Joystick app we created above.

/settings.development.json

{
  "config": {
    "databases": [
      {
        "provider": "mongodb",
        "users": true,
        "options": {}
      }
    ],
    "i18n": {
      "defaultLanguage": "en-US"
    },
    "middleware": {},
    "email": {
      "from": "<Default Email To Send From>",
      "smtp": {
        "host": "smtp.postmarkapp.com",
        "port": 587,
        "username": "<Paste Your Server API Token Here>",
        "password": "<Paste Your Server API Token Here>"
      }
    }
  },
  "global": {},
  "public": {},
  "private": {}
}

In this file, under the config object, locate the email object. Here, for the username and password fields, we want to paste in the value you just copied (as we'll see, when sending email, this is how Postmark authenticates your account and knows to send the email from your Postmark account).

Next, for the host field we want to enter smtp.postmarkapp.com and for the port we want to enter the number 587 (this is the secure email port). Finally, for the from field, you want to enter the default email address you're going to be sending from (e.g., support@myapp.com). For this tutorial, it's wise to use the same email address that you created your Postmark account with as they will enable only that address for sending email by default. Email sent from any other address will be rejected until Postmark approves your account (they have an approval process that's fairly quick and helps to eliminate spammers from harming the sender reputation for legitimate accounts).

Once this is set, back on the Postmark site, we want to head to the Sender Signatures page to make sure that the email you just entered for from above is set up.

You should check the email address you signed up with as Postmark should have sent you a "Verify Email Address" email. If you click the link in this email, it will enable sending from this address.

If it's on the list, just check that email address and click the verify link. If the address you entered is not on the list, head to the "Add a New Signature" page and add it so Postmark doesn't block your messages.

Once this is done—and your address is verified—sending should work as expected. If it's not working, Postmark will tell you in the "Activity" tab of your server.

That's all we need to do for config. Now, let's jump into wiring up our email template.

Creating an email template

Just like pages and other components in Joystick, email templates are authored using Joystick components. This means that you can use the same familiar API you use to build your application's UI to write your emails (at the end of the day, you're still just writing HTML and CSS for your emails so there's no learning curve).

In your project, now, we want to create a special folder email at the root of your app and then in that folder, add a file invoice.js:

/email/invoice.js

import ui from '@joystick.js/ui';

const Invoice = ui.component({
  render: () => {
    return `
      <div>
      </div>
    `;
  },
});

export default Invoice;

For our example, we'll be building an email template that represents an invoice for a customer, taking in an address and some line items as props. Because the content isn't terribly important here, let's go ahead and populate our skeleton template above with our content and walk through what it's doing:

/email/invoice.js

import ui from '@joystick.js/ui';

const Invoice = ui.component({
  render: ({ props, each }) => {
    return `
      <div class="invoice">
        <h4>Invoice</h4>
        <address>
          ${props.name}<br />
          ${props.address}<br />
          ${props.suite}<br />
          ${props.city}, ${props.state} ${props.zipCode}
        </address>
        <table>
          <thead>
            <tr>
              <th class="text-left">Item</th>
              <th>Price</th>
              <th>Quantity</th>
              <th>Total</th>
            </tr>
          </thead>
          <tbody>
            ${each(props.items, (item) => {
              return `
                <tr>
                  <td>${item.description}</td>
                  <td class="text-center">${item.price}</td>
                  <td class="text-center">x${item.quantity}</td>
                  <td class="text-center">${item.price * item.quantity}</td>
                </tr>
              `;
            })}
          </tbody>
          <tfoot>
            <tr>
              <td colspan="2"></td>
              <td colspan="1" class="text-center"><strong>Total</strong></td>
              <td colspan="1" class="text-center">
                ${props.items.reduce((total, item) => {
                  total += (item.price * item.quantity);
                  return total; 
                }, 0)}
              </td>
            </tr>
          </tfoot>
        </table>
      </div>
    `;
  },
});

export default Invoice;

Updating our render() function to include our full HTML here, we've got three core components:

  1. An <h4></h4> tag describing our template as an "Invoice."
  2. An <address></address> tag rendering the address of the person we're sending the invoice to.
  3. A <table></table> to render out line items.

For our render() function signature, we've added a single argument that's being destructured (in JavaScript, this means to "pluck off" properties from an object, assigning those properties to variables in the current scope of the same name) to give us two variables: props and each.

The first, props, will contain the props or properties that we pass to our template when we send our email. The second, each is a function (known as a render function in Joystick) which helps us to loop over an array and return some HTML for each item in the array. Here, for each of our line items in props.items we want to output a table row outputting the content of that item (and doing some multiplication on its price and quantity fields).

The only other thing to call attention to here is in the <tfoot></tfoot> part of our table. Here, we're adding up all of the line items using a plain JavaScript Array.reduce() function to "reduce down" the array of items into a single value, in this case, an integer representing the total of all items in the props.items array.

Confused by .reduce() and want to go in-depth? Give this tutorial a read.

That's it for our HTML. Now, real quick before we move on to sending, let's add in some CSS to pretty things up a bit:

/email/invoice.js

import ui from '@joystick.js/ui';

const Invoice = ui.component({
  css: `
    .invoice {
      padding: 20px;
    }

    h4 {
      margin: 0;
      font-size: 20px;
    }

    address {
      margin: 20px 0;
    }

    .text-left {
      text-align: left;
    }

    .text-center {
      text-align: center;
    }

    table {
      width: 100%;
      border: 1px solid #eee;
    }

    table tr th,
    table tr td {
      border-bottom: 1px solid #eee;
      padding: 10px;
    }

    table tfoot tr td {
      border-bottom: none;
    }
  `,
  render: ({ props, each }) => {
    return `
      <div class="invoice">
        ...
      </div>
    `;
  },
});

export default Invoice;

Not much happening here: just cleaning up spacing and adding some borders to our table so it looks more presentable and easy to read in our email.

What's neat about this is that when we send our email, Joystick will automatically take the CSS we've just added and inline it in our HTML (this means adding style attributes to the appropriate elements in our HTML) to make it more friendly for HTML email clients.

With that, next, let's move on to testing and sending our email.

Sending an email

Before we wire up our send, real quick, let's take a look at how we can test and preview our HTML email. Because our email is just a Joystick component, just like any other page or component in our app, we can render it using the res.render() function Joystick gives us in our router.

/index.server.js

import node from "@joystick.js/node";
import api from "./api";

node.app({
  api,
  routes: {
    "/": (req, res) => {
      res.render("ui/pages/index/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/email/invoice": (req, res) => {
      res.render(`email/invoice.js`, {
        props: {
          name: 'Bert',
          address: '1234 Sesame St.',
          suite: '#123',
          city: 'Sesame',
          state: 'ST',
          zipCode: '12345',
          items: [
            { description: 'Basketball', price: 10.00, quantity: 2 },
            { description: 'Football', price: 7.00, quantity: 5 },
            { description: 'Baseball', price: 4.95, quantity: 20 }
          ],
        },
      });
    },
    "*": (req, res) => {
      res.render("ui/pages/error/index.js", {
        layout: "ui/layouts/app/index.js",
        props: {
          statusCode: 404,
        },
      });
    },
  },
});

In our /index.server.js file created for us when we ran joystick create app earlier, here, we're adding a route called /email/invoice and calling to res.render('email/invoice.js'). This tells Joystick that we want to render the component at the specified path. Additionally, because we know our component will expect some props, via the options object passed as the second argument to res.render() we're specifying a props value which is passed an object of props we want to hand down to our component.

Here, we're passing all of the expected values for our template, specifically, the address of the recipient and the items they ordered. Now, if we open up http://localhost:2600/email/invoice in a browser, we should see our template rendered to screen:

UhVnnxDaZUVLA0y8/cXh8FevMGe2LwfDW.0
Rendering our email template in the browser.

While this won't give us a perfect representation of how our email will look in an email client (email clients are notoriously difficult and inconsistent for rendering/styling), it's a great way to debug our template without having to send a bunch of emails.

Now that we can verify our template is working, next, let's actually send it off. To do it, we're going to use the email.send() method from @joystick.js/node:

/index.server.js

import node, { email } from "@joystick.js/node";
import api from "./api";

node.app({
  api,
  routes: {
    "/": (req, res) => {
      res.render("ui/pages/index/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/email/send": (req, res) => {
      email.send({
        to: 'ryan.glover@cheatcode.co',
        from: 'business@cheatcode.co',
        subject: 'Invoice',
        template: 'invoice',
        props: {
          name: 'Bert',
          address: '1234 Sesame St.',
          suite: '#123',
          city: 'Sesame',
          state: 'ST',
          zipCode: '12345',
          items: [
            { description: 'Basketball', price: 10.00, quantity: 2 },
            { description: 'Football', price: 7.00, quantity: 5 },
            { description: 'Baseball', price: 4.95, quantity: 20 }
          ],
        },
      });
      res.send('Sent');
    },
    "/email/invoice": (req, res) => {
      ...
    },
    "*": (req, res) => {
      res.render("ui/pages/error/index.js", {
        layout: "ui/layouts/app/index.js",
        props: {
          statusCode: 404,
        },
      });
    },
  },
});

Up top, we've imported the email object from @joystick.js/node and down in our routes, we've added an additional route /email/send (this just makes it easy to send—in reality you'd want to call email.send() in response to real user behavior in something like a setter endpoint) and inside, we're calling to email.send(). This function will send our email using the SMTP connection we set up earlier (via Postmark if you're following along or whatever provider you specified).

Here, we pass a few different values:

  1. to which is the email address we want to send our test email to.
  2. from which is the email we want to send from (if you omit this, Joystick will use the from you specified in your config.email.from field in /settings.development.json).
  3. subject which is the subject line the recipient will see in their inbox.
  4. template which is the name of the file under the /email directory containing the template we want to use.
  5. props which are the props we want to pass to our template before rendering/sending.

That's it! To make sure our route responds in a browser when we call it, we call to res.send() passing a string "Sent" to notify us that the code has been called properly.

Assuming that our SMTP configuration is correct, if we visit http://localhost:2600/email/send in our browser, after a few seconds we should receive our email at the specified recipient.

Wrapping Up

In this tutorial, we learned how to create an email template using Joystick components. We learned how to wire up the component itself, accepting props, and how to style the template using CSS. Next, we learned how to test our email template in the browser to make sure it looked right and finally, how to send it off using email.send() via the SMTP service Postmark.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode