<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Seth Carney</title>
    <description>The latest articles on Forem by Seth Carney (@scarney81).</description>
    <link>https://forem.com/scarney81</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F513330%2Ff5edf841-563c-4248-b191-0b75cad222a2.jpeg</url>
      <title>Forem: Seth Carney</title>
      <link>https://forem.com/scarney81</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/scarney81"/>
    <language>en</language>
    <item>
      <title>Build Multi-Channel Notifications in Your AWS Stack using a Node.js Example</title>
      <dc:creator>Seth Carney</dc:creator>
      <pubDate>Mon, 13 Sep 2021 18:53:41 +0000</pubDate>
      <link>https://forem.com/courier/how-to-set-up-multi-channel-notifications-in-your-aws-stack-4oa</link>
      <guid>https://forem.com/courier/how-to-set-up-multi-channel-notifications-in-your-aws-stack-4oa</guid>
      <description>&lt;p&gt;How to Set Up Multi-Channel Notifications in Your AWS Stack&lt;br&gt;
In this article, we’ll walk through an example architecture for building your own notification service with AWS, and show you how to implement it in Node.js. We’ll also discuss a few considerations related to using AWS services for notifications.&lt;/p&gt;

&lt;p&gt;Let’s dive in!&lt;/p&gt;
&lt;h2&gt;
  
  
  Why build multi-channel notifications?
&lt;/h2&gt;

&lt;p&gt;Notifications are core to many software products today, from apps to e-commerce stores. For example, Instagram would not last without notifications, since users would probably not keep it open just to monitor activity.&lt;/p&gt;

&lt;p&gt;Multi-channel notifications become a strategic concern when companies realize that they cannot cater to their users, who are present on a variety of channels, with a one-size-fits-all approach. For instance, a user might prefer a summary of travel forum activity over email, since it works well for asynchronous communication. But for an activity requiring immediate attention, like a change to a flight schedule, a company would likely trade the email notification for a push notification, which the person concerned would likely see sooner.&lt;/p&gt;

&lt;p&gt;Ultimately, multi-channel notifications can affect your bottom-line: companies that tailor their notifications based on user needs tend to enjoy higher user engagement.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why use AWS services for multi-channel notifications?
&lt;/h2&gt;

&lt;p&gt;If you’re building multi-channel notifications into your project, the main reason you might go with AWS services is if you’re already using AWS for the rest of your infrastructure. If you already have AWS experience, it makes sense to build notifications in AWS since you’ll already be familiar with the AWS APIs and know your way around the AWS Management Console.&lt;/p&gt;

&lt;p&gt;Amazon Web Services offers two products for end-user notifications: &lt;a href="https://aws.amazon.com/ses/"&gt;SES&lt;/a&gt; and &lt;a href="https://aws.amazon.com/sns/"&gt;SNS&lt;/a&gt;. Both services have a pay-per-use pricing model that’s ideal for companies that want to start small and scale up their AWS use as the business grows.&lt;/p&gt;

&lt;p&gt;Amazon Simple Email Service (SES) is an API for sending emails and managing email lists. SES’s main API endpoints are focused on &lt;a href="https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html"&gt;sending emails&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetContactList.html"&gt;managing email contacts&lt;/a&gt;. The service also includes more advanced endpoints related to deliverability, like managing the &lt;a href="https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetDedicatedIps.html"&gt;dedicated IP addresses&lt;/a&gt; from which SES sends your emails.&lt;/p&gt;

&lt;p&gt;Amazon Simple Notification Service (SNS) is an API for sending notifications to applications and people. For many developers, the key to SNS is the “people” part—ability to send push and SMS messages to customers. SNS’s API endpoints allow you to send &lt;a href="https://docs.aws.amazon.com/sns/latest/dg/sms_publish-to-phone.html"&gt;individual&lt;/a&gt; messages, but most of the service’s functionality is built around &lt;a href="https://docs.aws.amazon.com/sns/latest/dg/sns-create-topic.html"&gt;SNS topics&lt;/a&gt; for sending batches of notifications over time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Multi-channel notification system architecture for AWS
&lt;/h2&gt;

&lt;p&gt;AWS SES and SNS provide APIs for sending notifications, but it's still the developer's job to tell the services which notifications to send, and to whom.&lt;/p&gt;

&lt;p&gt;Here's an example architecture for building out a notification system for AWS:&lt;br&gt;
&lt;a href="//images.contentful.com/z7iqk1q8njt4/5pEp1areRIli3KebtQDN2E/a2fb8065cca50d3a2a056618d76e881f/notifications-aws-stack-1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/5pEp1areRIli3KebtQDN2E/a2fb8065cca50d3a2a056618d76e881f/notifications-aws-stack-1.png" alt="notifications-aws-stack-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A common pattern in a &lt;a href="https://en.wikipedia.org/wiki/Service-oriented_architecture"&gt;service-oriented architecture&lt;/a&gt; is to extract notification logic to create a standalone service. In our example architecture, the notification service contains a few core pieces of functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Templates: most notifications you send follow a standardized format. Templates allow you to create that format once and then replace placeholders with user information.&lt;/li&gt;
&lt;li&gt;Error handling: when a notification cannot be delivered, whether because the end user is unreachable or the notification APIs are down, you’ll likely need to try and resend the notification.&lt;/li&gt;
&lt;li&gt;Preferences: you’ll need to store user choices for message categories such as account-related notifications or marketing messages.&lt;/li&gt;
&lt;li&gt;User profiles: you’ll want to store user emails and phone numbers.&lt;/li&gt;
&lt;li&gt;Notification routing: this is the core logic for deciding which notification should be sent based on event type, user preference, user location, and other factors.&lt;/li&gt;
&lt;li&gt;Tracking: to analyze the effectiveness of your notifications, you’ll need to track interactions with individual notifications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The notification service usually needs to expose an API to which other services can connect. The API can be synchronous, available through an &lt;a href="https://en.wikipedia.org/wiki/Representational_state_transfer"&gt;HTTP REST&lt;/a&gt; or &lt;a href="https://grpc.io/"&gt;GRPC&lt;/a&gt; endpoint, or asynchronous and based on a message broker, like &lt;a href="https://www.rabbitmq.com/"&gt;RabbitMQ&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Beyond the code in the notification service itself, developers often need to collect metrics in a centralized metrics store. Team members in charge of the notification service track the service’s status through metrics like the number of notifications sent per hour, or the share of API errors from various providers. If the service has a queue-based API, the queue size would also be published as a metric. Service operators could use these metrics to understand whether the service is behaving normally, or if there are issues requiring attention from the development team.&lt;/p&gt;

&lt;p&gt;While the service connects to third-party notification-sending services (SES and SNS, in our case), you can extend it to support other providers in the future.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sample implementation of a notification service
&lt;/h2&gt;

&lt;p&gt;Let’s walk through a notification service implementation in code. For this example, we’ll go with a Node.js web framework called &lt;a href="https://www.fastify.io/"&gt;Fastify&lt;/a&gt;. It’s a lightweight framework that’s optimized for speed, which is exactly what we need in an internal REST service.&lt;/p&gt;

&lt;p&gt;We’ll implement a REST API as our interface to the notification service, but your implementation can have a different structure—it can be a &lt;a href="https://grpc.io/"&gt;GRPC&lt;/a&gt; API, or it can consume messages off of a &lt;a href="https://www.rabbitmq.com/"&gt;RabbitMQ&lt;/a&gt; queue.&lt;/p&gt;

&lt;p&gt;In case you’d like to follow along, our complete example implementation is available in the &lt;a href="https://github.com/trycourier/aws-notification-service"&gt;notification-service repository&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;We start by cloning the repo and installing all required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone git@github.com:/trycourier/aws-notification-service.git
$ cd notification-service
$ npm install

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic of our example notification service is contained in the &lt;a href="https://github.com/trycourier/aws-notification-service/blob/main/fastify/index.js"&gt;fastify/index.js file&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we'll define an email template. We’ll use AWS SES’s built-in template functionality, but you could use a library like &lt;a href="https://github.com/janl/mustache.js"&gt;mustache.js&lt;/a&gt; or build your own templating system instead. Our template map contains the fields that SES &lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/ses-examples-creating-template.html"&gt;requires&lt;/a&gt; in their API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// fastify/index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paramsForTemplateCreation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;TemplateName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MigrationConfirmation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;HtmlPart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;h1&amp;gt;Hello {{name}},&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;You are confirmed for the winter migration to &amp;lt;a href='https://en.wikipedia.org/wiki/{{location}}'&amp;gt;{{location}}&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SubjectPart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Get ready for your journey, {{name}}!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;TextPart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dear {{name}},&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;You are confirmed for the winter migration to &amp;lt;a href='https://en.wikipedia.org/wiki/{{location}}'&amp;gt;{{location}}&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll need to create this template on AWS, so we’ll add a function for creating a template using the AWS SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createTemplate&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sesClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CreateTemplateCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll get an error if we try to create a template that already exists in the system, so we wrap the createTemplate() function in a try/catch block. In this block, we’ll try to get the template with the relevant name, and if that fails we’ll create it in AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createTemplateIfNotExists&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;TemplateName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TemplateName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;templateExists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sesClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;GetTemplateCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;createTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won’t add many other features to our simple templating system for now.&lt;/p&gt;

&lt;p&gt;Next, let’s take care of the notification sending. Because we’re using AWS SES templates, it makes sense to use the SES &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SES.html#sendTemplatedEmail-property"&gt;sendTemplatedEmail endpoint&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;sendTemplatedEmail&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sesClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SendTemplatedEmailCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="c1"&gt;// For unit tests&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this function, we’ll simply pass the parameters that we receive to the sendTemplatedEmail API endpoint. Let’s also create a set of placeholder parameters so that we can easily call the sendTemplatedEmail function when we need to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paramsForTemplatedEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ToAddresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kingfisher@example.imap.cc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nightjar@example.imap.cc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MigrationConfirmation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;TemplateData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{ "name":"Alaric", "location": "Mexico" }&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="cm"&gt;/* required */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ReplyToAddresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it’s time to define the API routes that our services will use to send notifications. We define the main route, /notify, by using &lt;a href="https://www.fastify.io/docs/latest/Routes/#url-building"&gt;Fastify’s URL shorthand&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/notify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;migration-confirmed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;sendTemplatedEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paramsForTemplatedEmail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;migration-confirmed email sent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event not configured&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we’re defining the POST /notify endpoint. Once the application receives a request to the /notify URL, it’ll parse out the following elements from the request body:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;userId: an internal user identifier.&lt;/li&gt;
&lt;li&gt;event: an event type requiring user notification.&lt;/li&gt;
&lt;li&gt;params: any additional parameters for building out notification contents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on the event value, we’ll need to decide which notification to send. Some products will have &lt;a href="https://www.reddit.com/r/ProductManagement/comments/gaqijn/slack_notification_flowchart/"&gt;complex notification routing logic&lt;/a&gt;, but we’ll start with a single switch statement. A long switch statement will become unmaintainable if you add many events, so this section should eventually be split into multiple functions.&lt;/p&gt;

&lt;p&gt;We only have migration-confirmed defined for now, and when that event occurs we want to send an email notification. Other calls to services, like AWS SNS, would go inside the statement that handles the migration-confirmed event.&lt;/p&gt;

&lt;p&gt;Beyond the /notify endpoint above, we can create additional endpoints as needed. For example, here are a few endpoints that your notification service will need (we’ll leave implementation up to you):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/subscriber&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phoneNum&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
  &lt;span class="c1"&gt;// handle new subscribers&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handling of new subscribers not yet implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/subscriber/:userId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;
  &lt;span class="c1"&gt;// unsubscribe user identified by userId from all emails&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handling of unsubscribes not yet implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/subscriber/:userId/preferences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;preferences&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
  &lt;span class="c1"&gt;// handle subscription preferences&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handling of preferences not yet implemented&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we tell our Fastify backend to listen on port 3000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🚀 Server ready at: http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s start the app and try it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm run dev
[nodemon] starting `node fastify/index.js`
{"level":30,"time":1628785943753,"pid":16999,"hostname":"notification-service","msg":"Server listening at http://127.0.0.1:3000"}
🚀 Server ready at: http://localhost:3000

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we’ll try issuing a POST request to the /notify endpoint. We’ll use &lt;a href="https://curl.se/"&gt;cURL&lt;/a&gt; for this purpose, but you can also use an app like &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -X POST \
   -H 'Content-Type: application/json' \
   -H 'Accept: application/json' \
   -d '{"userId": 123, "event": "migration-confirmed"}' \
   localhost:3000/notify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see the notification email land in our inbox shortly after calling the endpoint:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/1lZyJhAMeXjIFbIDyoMAOE/7f6b1509e987cbd6a38a2d0004b11ced/notifications-aws-stack-2.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/1lZyJhAMeXjIFbIDyoMAOE/7f6b1509e987cbd6a38a2d0004b11ced/notifications-aws-stack-2.png" alt="notifications-aws-stack-2"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Email notification in our test inbox.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nice work! We now have a working notification service with AWS.&lt;/p&gt;
&lt;h2&gt;
  
  
  Limitations to AWS notification solutions
&lt;/h2&gt;

&lt;p&gt;Before you jump into the design and implementation phases for your AWS-backed notification service, consider the following limitations to AWS services.&lt;/p&gt;
&lt;h3&gt;
  
  
  AWS services are “raw”
&lt;/h3&gt;

&lt;p&gt;Think about SES and SNS as services with APIs closely resembling the underlying notification protocols. Both services will require you to implement most features that are not core to notification sending.&lt;/p&gt;

&lt;p&gt;For example, SES requires you to manually compose &lt;a href="https://gist.github.com/tylermakin/d820f65eb3c9dd98d58721c7fb1939a8"&gt;multi-part messages&lt;/a&gt; if you’re looking to send attachments. It does not offer a seamless API that would automatically take care of the attachments—you’ll need to implement that yourself on top of the SES API.&lt;/p&gt;

&lt;p&gt;Contact management is another area in which SES requires additional work. If you opt for SES-managed lists, you’ll need to build the logic for adding and removing subscribers for each email list.&lt;/p&gt;

&lt;p&gt;SNS is also limited in terms of developer usability. For example, notifications that cannot be delivered end up in a dead-letter queue, which you’ll need to monitor for retries.&lt;/p&gt;
&lt;h3&gt;
  
  
  Error-checking is laborious
&lt;/h3&gt;

&lt;p&gt;Another aspect of AWS’s “rawness” is error-checking. For example, you need to check for email bounces or undelivered push notifications and manage them yourself.&lt;/p&gt;

&lt;p&gt;As we mentioned above, any SNS notification that cannot be delivered will end up in a &lt;a href="https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html"&gt;dead-letter queue&lt;/a&gt;. This queue is an &lt;a href="https://aws.amazon.com/sqs/"&gt;Amazon Simple Queue Service (SQS)&lt;/a&gt; queue, and you’ll need to implement functionality to listen for messages on this channel.&lt;/p&gt;

&lt;p&gt;When you get an “unsuccessful notification” message in this queue, you’ll need to decide whether to try the notification again (and schedule it in your notification service accordingly), or to send it through an alternative notification method (a different channel or provider). You’ll also need to track which push targets and email addresses are consistently unresponsive and thus need to be removed from future notification lists.&lt;/p&gt;

&lt;p&gt;You can build error-handling functionality with SNS and SES, as the necessary details for each error case are available on both services’ APIs. But you’ll also need to implement error handling yourself.&lt;/p&gt;
&lt;h3&gt;
  
  
  You’ll need to build a templating engine
&lt;/h3&gt;

&lt;p&gt;While you can readily implement simple email and notification templates using tools like mustache.js, complex templates, like elaborate HTML emails, are another story.&lt;/p&gt;

&lt;p&gt;You’ll need to test your templates to ensure they work as expected on all supported devices and clients. Email formatting is difficult to get right, so we recommend budgeting extra time to develop and test your templates.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Courier improves the notification experience for AWS-based customers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.courier.com"&gt;Courier&lt;/a&gt; is an API for multi-channel notifications, and many of our customers use AWS. We offer our customers an opportunity to use AWS services for notifications without having to build all of the additional functionality on top of AWS services themselves.&lt;/p&gt;

&lt;p&gt;Here's how you might send an email and SMS notification with Courier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CourierClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@trycourier/courier&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;courier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CourierClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authorizationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;AUTH_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// get from the Courier UI&lt;/span&gt;

&lt;span class="c1"&gt;// Example: send a message supporting email &amp;amp; SMS&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messageId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;courier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;EVENT_ID&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// get from the Courier UI&lt;/span&gt;
  &lt;span class="na"&gt;recipientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;RECIPIENT_ID&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// usually your system's User ID&lt;/span&gt;
  &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;kingfisher@example.imap.cc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;555-228-3890&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// optional variables for merging into templates&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Courier handles all communication with AWS on the backend and offers a number of additional advantages to reduce your implementation work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notification Designer: we offer a web interface for  designing notification templates, allowing users to create and edit notification templates without redeploying any code. This reduces the engineering work required to get new notifications added to a product.&lt;/li&gt;
&lt;li&gt;Automated handling of unsubscribes and errors: Courier handles notification preferences and automatically adjusts notification flows for unsubscribing users. There’s no need to monitor dead-letter SQS queues.&lt;/li&gt;
&lt;li&gt;Multi-provider: down the line, you may want to switch from SNS and SES, or add an additional provider for sending international SMS messages. Courier integrates with &lt;a href="https://www.courier.com/providers"&gt;20+ providers&lt;/a&gt;, all using the same API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we presented our suggested notification service architecture for integrating with AWS SES and SNS.&lt;/p&gt;

&lt;p&gt;It’s easy to get started with Courier to orchestrate notifications in your AWS services.&lt;/p&gt;

&lt;p&gt;We’re offering a free plan with up to 10,000 notifications/month, and we don’t require your credit card to get started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.courier.com/signup"&gt;Sign up for free today!&lt;/a&gt; &lt;/p&gt;

</description>
      <category>notifications</category>
      <category>multichannel</category>
      <category>aws</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why we are moving off REST and implementing GraphQL</title>
      <dc:creator>Seth Carney</dc:creator>
      <pubDate>Tue, 24 Nov 2020 19:38:17 +0000</pubDate>
      <link>https://forem.com/courier/why-we-are-moving-off-rest-and-implementing-graphql-3fbh</link>
      <guid>https://forem.com/courier/why-we-are-moving-off-rest-and-implementing-graphql-3fbh</guid>
      <description>&lt;h2&gt;
  
  
  Why we decided to transition to GraphQL
&lt;/h2&gt;

&lt;p&gt;When we started building &lt;a href="https://www.courier.com/"&gt;Courier&lt;/a&gt;, we investigated &lt;a href="https://graphql.org/"&gt;GraphQL&lt;/a&gt;, but the options for running a serverless version of &lt;a href="https://www.apollographql.com/"&gt;Apollo&lt;/a&gt; (the technology we wanted to use) were limited and less stable. Because we don’t run EC2 or have dedicated servers, that was a major consideration for us. However, that’s changed quite substantially since we first looked at Apollo. Since then, we’ve been able to start transitioning both our internal and &lt;a href="https://docs.courier.com/reference/introduction"&gt;external APIs&lt;/a&gt; to GraphQL. &lt;/p&gt;

&lt;p&gt;I’ll explain the reasoning behind this below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations of using REST
&lt;/h3&gt;

&lt;p&gt;REST has been around for a long time, and today it’s still the most widely-accepted way to write an API. REST is a specification that sits on top of HTTP. API calls are structured around objects (like profiles, preferences, and templates) using dedicated HTTP endpoints. For example, if you wanted to expose a way to programmatically manipulate your user profiles, you might have a REST endpoint &lt;code&gt;/user/{userId}&lt;/code&gt; which can be queried to perform CRUD operations using HTTP &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, etc. Writing a REST API is pretty straightforward – but REST can be tricky to use as an API consumer. &lt;/p&gt;

&lt;p&gt;First, REST wasn’t designed for complex sequences of operations that don’t fit neatly into the CRUD bucket. It’s not easy to update two objects at the same time, for example, and even retrieving data in certain scenarios can require multiple calls and branching logic as one endpoint might have to call another one. Another downside of REST is it puts a lot of responsibility on the API consumer (which may be your internal developers or your customers) to know how the underlying data is structured. That’s not optimal for several reasons. &lt;/p&gt;

&lt;p&gt;The API calls aren’t oriented to the common actions that the user wants to take. They’re structured rigidly around &lt;em&gt;your&lt;/em&gt; objects. That means someone might have to call the same REST endpoint to set a label and add a collaborator, even though these are two completely different use cases. Another reason it’s not a good idea to structure your API around how your data is organized is because things change. Changes to your data are inevitable and it’s hard to adapt REST APIs to these changes (if you do find yourself in this situation, here's how we approached &lt;a href="https://www.courier.com/blog/what-to-consider-when-standardizing-your-rest-apis"&gt;standardizing our REST API&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of moving to GraphQL
&lt;/h3&gt;

&lt;p&gt;GraphQL is a query language with a very developer-friendly approach to building APIs. It's based on the idea that the API consumer shouldn’t have to know &lt;em&gt;anything&lt;/em&gt; about how the data is stored internally. Instead, you describe your data’s relational schema and the consumer can query that nested data from a single endpoint that never changes. GraphQL also conforms to the idea of CQRS, or command-query responsibility separation – put simply, it means the way that you query data is different from the way you mutate data. &lt;/p&gt;

&lt;p&gt;One of the things I like best about GraphQL is, as a side effect of implementing it, you’re forced to live by some of those rules of software engineering that you really should be living by. You have to think about your data holistically and you don’t end up with a bunch of poorly-designed endpoints lying around as the result of shortcuts you took to meet deadlines. &lt;/p&gt;

&lt;p&gt;Because of how it’s built, GraphQL is really good at versioning: you can mark functionality as deprecated and you can change the underlying infrastructure without breaking existing integrations (and without the consumer even knowing). GraphQL also has a solid caching layer, which reduces our total operational costs because we end up not hitting our database as much. Because we’re a serverless shop, we will actually be implementing our caching layer through &lt;a href="https://aws.amazon.com/elasticache/"&gt;ElastiCache&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using GraphQL at Courier
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How we decided which technology to use
&lt;/h3&gt;

&lt;p&gt;As I mentioned earlier, we thoroughly researched the options for implementing GraphQL and kept an eye on possible solutions. There were two main options that emerged for our use case: &lt;a href="https://aws.amazon.com/appsync/"&gt;AWS AppSync&lt;/a&gt; and &lt;a href="https://www.apollographql.com/"&gt;Apollo GraphQL&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We evaluated AppSync because we’re an AWS customer, we use cloud formations, and it was appealing to be able to stand something up quickly. But there were some core security choices we made when implementing multi-tenancy in &lt;a href="https://aws.amazon.com/cognito/"&gt;Amazon Cognito&lt;/a&gt; that made the switch to AppSync difficult. We realized AppSync wasn’t going to work for us unless we changed some of those fundamental decisions.&lt;/p&gt;

&lt;p&gt;But that wasn’t the only reason we decided to go with Apollo. Compared to AppSync, which uses the Apache Velocity Template Language (VTL), Apollo is just JavaScript. When we work with Apollo, we don’t have to do a lot of the mental context-switching that happens when you use different languages. Not to mention, Apollo is popular for a reason: it’s a rock-solid product that’s constantly evolving and has a growing and supportive community of users. Finally, we chose Apollo for the &lt;a href="https://www.apollographql.com/docs/federation/"&gt;Apollo Federation&lt;/a&gt;, which will help us grow our Graph without affecting our performance as our product scales.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Our roadmap for transitioning to GraphQL
&lt;/h3&gt;

&lt;p&gt;Right now, we’ve moved some of our internal APIs to GraphQL, such as the infrastructure for accessing users and tenants. We’re also building all new features with GraphQL as well.&lt;/p&gt;

&lt;p&gt;While it'll be some time before we move all our internal APIs to GraphQL, we have plenty of important candidates for this transition. One key use case is autosave during template creation in &lt;a href="https://help.courier.com/en/articles/4316955-notification-design-studio-overview"&gt;our notifications designer&lt;/a&gt;. When you're editing a template, you can add content blocks like text or images, add notification channels like email or SMS, and add conditional logic (just to name a few examples) and, as soon as you make a change, it gets autosaved. Behind the scenes, these edits are funneled through a common processor. &lt;/p&gt;

&lt;p&gt;One of the problems in REST is it’s difficult to do partial updates. The various components end up having to send the whole template resource when they want to update a single field. Sure, you can implement PATCH endpoints, but those come with their own complications. When you factor in doing validation on the full object with every call, autosave has the potential to become an expensive operation. Moving autosave operations to GraphQL mutations will help us solve this problem outside the constraints of a traditional REST API design and more closely represent the types of actions our users are taking.&lt;/p&gt;

&lt;p&gt;As we move all our internal infrastructure to GraphQL, our ultimate goal is to expose a GraphQL interface to our customers, along with an explorer that will make it so consumers can interact with our schema right from the browser.&lt;/p&gt;




&lt;p&gt;If you’re interested in working with GraphQL, we’re hiring engineers at Courier. &lt;a href="https://jobs.lever.co/trycourier"&gt;You can check out our open roles here&lt;/a&gt; – we hope to hear from you! &lt;/p&gt;

</description>
      <category>graphql</category>
      <category>apollo</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
