How I created a check out later component

Boston CartwrightBoston Cartwright | March 13, 2021 | react
12 min read | ––– views
Photo by AbsolutVision on Unsplash
Photo by AbsolutVision on Unsplash

Let your readers decide when to read your post!

Want to check this out later?

Introduction

Often times when I am browsing the web, I do it in small batches of the day. For example, two minutes while waiting for a meeting to start, or for five minutes waiting for my wife to get ready.

During this time, I typically will come across a number of links or articles I don't have enough time to read at that moment but want to check out later. There are lots of solutions to fix this, including adding bookmarks, read later lists, and more.

However, I found the most useful was to get a notification about it later. I wanted to add that functionality to my posts as well, and this article is how I came up with the above solution.

Research

After some thought, I decided that an email later is probably the best way to send a reminder to take a deeper look into a post. That way, the reader can even prioritize it further in their inbox.

I started looking at ways to send an email programmatically and I came across Twilio's SendGrid, which sounded to fit my use case perfectly.

SendGrid

I wanted to use my own domain to send these emails, and SendGrid has some awesome documentation and walkthroughs on how to do this. This included setting up some CNAME records for my domain, which was easy to do since I manage my domain through Netlify.

Once I completed this, I set up a single sender and was ready to send my first email.

Sending my first email

Now that I was set up, I needed an API endpoint to send emails from. I'm using Next.js, so all I needed to do was create a /pages/api/remind.js file.

Following SendGrid's documentation using their Node.js library, this was extremely simple to get started. Here is what my first iteration looked like:

import sendgrid from '@sendgrid/mail'

export default async function remind(req, res) {
  sendgrid.setApiKey(process.env.SENDGRID_API_KEY)

  const { slug, title, email, when } = req.body

  const msg = {
    to: email, // Change to your recipient
    from: 'boston@remind.bostonc.dev', // Change to your verified sender
    subject: 'Sending with SendGrid is Fun',
    text: `and easy to do anywhere, even with Node.js`,
    html: '<strong>and easy to do anywhere, even with Node.js</strong>',
  }

  const response = await sendgrid.send(msg)

  console.log(response)

  res.status(200).json({})
}

I then called the endpoint, and it worked great!

first sendgrid email

Now it was time to spruce it up a little.

Sending the email

Originally I thought it would be simple to design an email template, after all it is just HTML.

Boy was I wrong.

I started to do some research on email HTML best practices, with my first thoughts being to find if there are specific HTML tags to use for accessibility. Upon doing this search, what I found was quite daunting.

Everyone recommended using HTML tables in order to achieve great layouts. 👀

I had heard that many years ago most website layouts were defined with tables, but I had never done it before. This was not going to be easy.

Luckily, to achieve the single column design I was planning on doing, I found a great template by tinabeans on GitHub.

Here is what my template ended up looking like:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>${TITLE}</title>
    <link rel="preconnect" href="https://fonts.gstatic.com" />
    <link
      href="https://fonts.googleapis.com/css2?family=Bowlby+One+SC&display=swap&text=Boston%20Cartwright"
      rel="stylesheet"
    />
    <style>
      @media only screen and (min-device-width: 541px) {
        .content {
          width: 540px !important;
        }
      }

      .header {
        font-family: 'Bowlby One SC', Verdana, Geneva, Tahoma, sans-serif;
        background-color: #00acbd;
        padding: 1.5rem;
        border-radius: 1rem;
        width: 80%;
        margin: 1rem auto;
      }

      body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
          Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
      }

      .m-center {
        margin-left: 2.5rem;
        margin-right: 2rem;
      }

      .no-m-t {
        margin-top: 0;
      }

      .no-m-b {
        margin-bottom: 0;
      }

      .text-gray {
        color: #6b7280;
      }

      .m-b-4 {
        margin-bottom: 2rem;
      }

      .m-t-6 {
        margin-top: 3rem;
      }

      img {
        margin-top: 2rem;
        box-shadow: 10px 10px 32px 0px rgba(0, 0, 0, 0.75);
      }

      .check-out {
        background-color: #00acbd;
        padding: 1rem;
        border-radius: 1rem;
        color: black;
        text-decoration: none;
        box-shadow: 10px 10px 32px 0px rgba(0, 0, 0, 0.75);
      }

      .check-out:hover {
        background-color: #007580;
      }
    </style>
  </head>

  <body>
    <!--[if (gte mso 9)|(IE)]>
  <table width="540" align="center" cellpadding="0" cellspacing="0" border="0">
    <tr>
      <td>
<![endif]-->

    <table
      class="content"
      align="center"
      cellpadding="0"
      cellspacing="0"
      border="0"
      style="width: 100%; max-width: 540px;"
    >
      <tr>
        <td style="text-align:center">
          <h1 class="header">Boston Cartwright</h1>
        </td>
      </tr>
      <tr>
        <td>
          <h2 class="m-center">Hello there!</h2>

          <p class="m-center">
            You requested to be reminded about this article earlier on ${DATE}
            at ${TIME}:
          </p>
        </td>
      </tr>
      <tr>
        <td style="text-align:center">
          <img src="${IMAGE}" style="max-width: 50%;border-radius: .5rem;" />
        </td>
      </tr>
      <tr>
        <td style="text-align:center">
          <h2 class="m-center no-m-b">${TITLE}</h2>
          <small class="m-center text-gray">${DATE}</small>

          <p class="m-center m-b-4">${EXCERPT}</p>
        </td>
      </tr>
      <tr>
        <td style="text-align:center">
          <a href="${LINK}" class="check-out">Check it out!</a>
        </td>
      </tr>
      <tr>
        <td>
          <hr class="m-t-6" />
        </td>
      </tr>
      <tr>
        <td style="text-align:center">
          <small class="m-center text-gray"
            >You received this email as a response to your request to be
            reminded about a page from
            <a href="https://bostonc.dev">bostonc.dev</a>. You will not receive
            another email unless you request one. For feedback or inquires, feel
            free to <a href="https://twitter.com/bstncartwright">reach out</a>.
          </small>
        </td>
      </tr>
    </table>

    <!--[if (gte mso 9)|(IE)]>
      </td>
    </tr>
</table>
<![endif]-->
  </body>
</html>

Now I was tasked with implementing a solution in order to populate the email template. This was simple to accomplish just with JavaScript's string interpolation.

Next I added some features to the API call, including choosing when the email is sent and more. Here is what it ended looking like:

Here is what it ended up looking like:

export default async function remind(req, res) {
  sendgrid.setApiKey(process.env.SENDGRID_API_KEY)

  const { slug, email, when } = req.body

  if (!emailRegex.test(email))
    return res.status(400).json({ error: `invalid email ${email}` })

  const data = getPostDataBySlug(slug)

  if (!data)
    return res
      .status(401)
      .json({ error: `unable to find post with slug ${slug}` })

  const html = ` { ...omitted for brevity }`

  const sendAt = getUnixTime(add(Date.now(), { hours: when }))

  const msg = {
    to: email,
    from: {
      email: 'boston@remind.bostonc.dev',
      name: 'Boston Cartwright',
    },
    subject: `Check out ${data.title} by Boston Cartwright`,
    html,
    sendAt,
  }

  const response = await sendgrid.send(msg)

  return res.status(200).json({})
}

Putting it all together

Now that I had a way to send emails, the next step was to build a component in which I could do so!

This was just a simple form with some radio buttons on when to be reminded, and an input field for the users email!

After inputting your email and choosing when to be reminded, the component makes a call to the API endpoint I created:

const onSubmit = async (event) => {
  event.preventDefault()
  setState('sending')
  const response = await fetch('/api/remind', {
    method: 'POST',
    headers: new Headers({
      'Content-Type': 'application/json',
    }),
    credentials: 'same-origin',
    body: JSON.stringify({
      slug,
      email: event.target.email.value,
      when: hours,
    }),
  })

  if (response.status !== 200) {
    setState('error')
    return
  }

  setState('success')
}

Thus, the reminder is sent.

At the top of this page you can see the final result! This was a super fun feature to add to my site.


What do you think? Let me know @bstncartwright ! 😀