<?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: Micah Zayner</title>
    <description>The latest articles on Forem by Micah Zayner (@artsarescis).</description>
    <link>https://forem.com/artsarescis</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%2F239805%2F3ccb6043-bf2b-4b01-b916-4c8f00c4326b.jpg</url>
      <title>Forem: Micah Zayner</title>
      <link>https://forem.com/artsarescis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/artsarescis"/>
    <language>en</language>
    <item>
      <title>Courier Inbox for web and mobile, a complete notification center</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Wed, 14 Jun 2023 07:42:59 +0000</pubDate>
      <link>https://forem.com/courier/courier-inbox-for-web-and-mobile-a-complete-notification-center-4ai4</link>
      <guid>https://forem.com/courier/courier-inbox-for-web-and-mobile-a-complete-notification-center-4ai4</guid>
      <description>&lt;p&gt;📣 &lt;strong&gt;Join the conversation on &lt;a href="https://www.producthunt.com/posts/courier-inbox-inapp-notification-center/"&gt;Product Hunt&lt;/a&gt; where we're discussing this groundbreaking launch and the future of in-app notifications.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.producthunt.com/posts/courier-inbox-inapp-notification-center?utm_source=badge-featured&amp;amp;utm_medium=badge&amp;amp;utm_souce=badge-courier-inbox-inapp-notification-center"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W9nZ7ov---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://api.producthunt.com/widgets/embed-image/v1/featured.svg%3Fpost_id%3D397476%26theme%3Dlight" alt="Courier Inbox: InApp Notification Center - A drop-in notification center for your web and mobile apps | Product Hunt" width="250" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A notification center inside of web and mobile apps is now an expectation. It’s a way to reach specific audiences or users with tailored messages and a way to boost engagement by bringing people back into the app. &lt;/p&gt;

&lt;p&gt;While Courier has been adding Inbox capabilities over the last couple years, we’re excited to announce a complete set of SDKs that span web and mobile. You can drop in a full-featured inbox to give your users a best-in-class notification center inside your app that works seamlessly with your existing notification flows.&lt;/p&gt;

&lt;p&gt;Here are just a few features of Courier Inbox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All the code needed&lt;/strong&gt; to build custom notification lists that show new messages when they arrive in real time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prebuilt-UI&lt;/strong&gt; that allows you to add a notification center to your app in minutes rather than months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API, event or manually triggered notifications&lt;/strong&gt; to your user’s inbox, or using inbox as one step in a multichannel sequence that includes email, Slack, or any channel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronization of message status&lt;/strong&gt; across web and mobile inboxes, as well as other channels used like email and SMS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User notification preferences&lt;/strong&gt; to offer more control to your users over what notifications they receive from your app and which channels work best for them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To see the full set of features for Courier Inbox, head to &lt;a href="https://www.courier.com/docs/inbox/"&gt;the docs&lt;/a&gt;. Or, if you’d like to see an example of what you can build with Courier, check out our Instagram-inspired IOS app, &lt;a href="https://www.courier.com/blog/introducing-puppygram-powered-by-courier-inbox-next-js-and-inngest"&gt;Puppygram&lt;/a&gt; or see how inbox is used at &lt;a href="https://www.courier.com/blog/courier-inbox-complete-notification-center-web-mobile/#real-world-examples"&gt;LaunchDarkly, DroneDeploy, and Oyster HR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/z7iqk1q8njt4/5TOVXtSVaIFCcad0L9i6QC/8f969b15380189effd777c71eeb4a88f/inbox_UI.gif" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/z7iqk1q8njt4/5TOVXtSVaIFCcad0L9i6QC/8f969b15380189effd777c71eeb4a88f/inbox_UI.gif" alt="inbox UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Options to build with Courier Inbox
&lt;/h2&gt;

&lt;p&gt;Previously, it was only feasible for large engineering teams to pull off a full-fledged notification inbox. Smaller teams often found themselves strained by the sheer scope of the project. Besides the engineering challenges, the design and user interface for the notification inbox also required thoughtful attention. Even though standard patterns exist in popular apps, like Instagram and Amazon, there's pressure to deliver a user-friendly experience that’s on-brand and doesn’t compromise the aesthetic integrity of the overall application.&lt;/p&gt;

&lt;p&gt;This was an important driver for Courier Inbox. We wanted to make sure we offered complete SDKs for web, Android and IOS, where a developer has access to the development tools and UI components so that the frontend is as well designed as the backend. Courier also offers &lt;a href="https://www.courier.com/docs/courier-designer/brands/how-to-brand-inbox-toast/"&gt;customizable branding design options&lt;/a&gt;, to match your inbox to your application's look and feel.&lt;/p&gt;

&lt;p&gt;Whether you're looking for a fully hosted implementation that requires minimal configuration or a composable, headless offering that you can tailor to your specific needs, Courier has got you covered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DroneDeploy
&lt;/h3&gt;

&lt;p&gt;As a leader in drone mapping software, &lt;a href="https://www.dronedeploy.com/"&gt;DroneDeploy&lt;/a&gt; offers services that capture interior and exterior visual data of a construction project or other environment, managing drone fleets for site documentation and analysis.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With Courier, we added a beautiful inbox and in-app push notifications in a matter of weeks. We used the great looking pre-built component to save even more time. Notifications are not our core competency, so it made complete sense to integrate rather than build out and support our own implementation. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;James Pipe&lt;/strong&gt;, VP of Product, DroneDeploy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;DroneDeploy has integrated Courier's inbox into their web application as a notification center. This has significantly improved customer communications, providing timely updates and important information to clients directly within the app. &lt;/p&gt;

&lt;p&gt;Top notifications sent through their inbox include status updates when new maps, walkthroughs, or progress videos are uploaded, and edit access is requested/approved/declined.&lt;/p&gt;

&lt;h3&gt;
  
  
  LaunchDarkly
&lt;/h3&gt;

&lt;p&gt;LaunchDarkly offers a feature management platform to developers and DevOps engineers. The platform offers multivariate feature flags, which enables A/B testing and the ability to incrementally roll out new features.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We were able to build the in-app notification experience that we wanted with excellent support and communication from the Courier staff. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lucy Wonsower&lt;/strong&gt;, Software Engineer, LaunchDarkly&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LaunchDarkly uses Courier's inbox for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managing event notifications for their feature management platform, such as requests for a new feature flag, or approvals of a feature flag request. The notification inbox speeds up these requests and approvals, thereby increasing value and customer retention.&lt;/li&gt;
&lt;li&gt;Sending team notifications such as team membership admin alerts, new user invites, welcome emails, and email verification messages.&lt;/li&gt;
&lt;li&gt;Billing notifications such as notifications that a user’s card was charged or that there was a failure to charge a card. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Oyster HR
&lt;/h3&gt;

&lt;p&gt;Another organization that has reaped significant benefits from Courier's notification inbox is &lt;a href="https://www.oysterhr.com/"&gt;Oyster HR&lt;/a&gt;. As a global employment platform, Oyster HR enables businesses to hire, pay, and manage employees from across the world without the need to set up business entities in each country.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using Courier Inbox, Oyster saved 4 engineers 3 months of work from not having to construct a notification infrastructure, design a UI, and develop them to spec. But the most important benefit is that as Oyster scales, we won't need to hire additional engineers solely to manage our notification framework as the volume grows. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amy Weinrieb&lt;/strong&gt;, Senior Product Manager, Oyster HR&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Integrating Courier's notification inbox
&lt;/h2&gt;

&lt;p&gt;One of the standout features of Courier's notification inbox is its simplicity of integration. Whether it's a mobile or a web application, a few lines of code are all you need to embed a fully functional notification inbox into your application. &lt;/p&gt;

&lt;p&gt;Here we have examples for adding an inbox to your web, IOS, and Android applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a notification center to your web application
&lt;/h3&gt;

&lt;p&gt;For web applications, you can use either the &lt;a href="https://www.courier.com/docs/inbox/web/react-sdk/"&gt;Courier React SDK&lt;/a&gt; or the vanilla &lt;a href="https://www.courier.com/docs/inbox/web/javascript-sdk/"&gt;JavaScript SDK&lt;/a&gt;. Here is a simple React example to illustrate this process.&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;//App.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CourierProvider&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/react-provider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Inbox&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/react-inbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Replace 'yourUserId' and 'YOUR_CLIENT_KEY' with your own details&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yourUserId&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;clientKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_CLIENT_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CourierProvider&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&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;clientKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clientKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Inbox&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CourierProvider&lt;/span&gt;&lt;span class="err"&gt;&amp;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;The above code is all it takes to integrate Courier's notification inbox into your React application. You just need to import CourierProvider and Inbox from &lt;a href="https://www.npmjs.com/package/@trycourier/react-provider"&gt;@trycourier/react-provider&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/@trycourier/react-inbox"&gt;@trycourier/react-inbox&lt;/a&gt; respectively. Then, you set the userId and clientKey within the CourierProvider, and place the Inbox component within it. Look at the &lt;a href="https://github.com/trycourier/courier-react/tree/main/packages/react-inbox"&gt;README&lt;/a&gt; on GitHub if you need more information.&lt;/p&gt;

&lt;p&gt;The userId and clientKey are crucial for Courier to know who's using the inbox and to ensure the proper messages are displayed. The clientKey is a public-facing key that can be found by navigating to "Settings &amp;gt; &lt;a href="https://app.courier.com/settings/api-keys"&gt;API keys&lt;/a&gt;" in the Courier app.&lt;/p&gt;

&lt;p&gt;The snippet uses the basic authentication provided by Courier. However, for enhanced security, it is recommended to use JWT Authentication. The complete guide to set up JWT Authentication can be found &lt;a href="https://www.courier.com/docs/inbox/authentication/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a notification center to your IOS application
&lt;/h3&gt;

&lt;p&gt;Here is an example for an iOS application written in Swift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Courier_iOS&lt;/span&gt;

&lt;span class="c1"&gt;// Create the view&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;courierInbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CourierInbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;didClickInboxMessageAtIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isRead&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markAsRead&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markAsUnread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nv"&gt;didClickInboxActionForMessageAtIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nv"&gt;didScrollInbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scrollView&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrollView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contentOffset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&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;span class="c1"&gt;// Add the view to your UI&lt;/span&gt;
&lt;span class="n"&gt;courierInbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translatesAutoresizingMaskIntoConstraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;courierInbox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Constrain the view how you'd like&lt;/span&gt;
&lt;span class="kt"&gt;NSLayoutConstraint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;courierInbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topAnchor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;courierInbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bottomAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bottomAnchor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;courierInbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leadingAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leadingAnchor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;courierInbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trailingAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trailingAnchor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;Courier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pk_prod_H12..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;clientKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"YWQxN..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"example_user_id"&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;This approach exemplifies the ”drop-in” method. You begin by importing the Courier_iOS package, add the view to your app, then sign in to your Courier account using your access token, client key, and user ID. The CourierInbox, for UIKit, and the CourierInboxView for SwiftUI, is then placed in your app how you’d like and, instantly embedding the notification inbox into your application with minimal effort. It's a quick and efficient way to get started, allowing developers to focus on building the core aspects of their app. This way, you can offer a powerful notification inbox to your users without spending significant time on implementation — and, more importantly, without compromising on the feature set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a notification center to your Android application
&lt;/h3&gt;

&lt;p&gt;For those interested in Android, here is the default inbox example for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="nl"&gt;inbox:&lt;/span&gt; &lt;span class="nc"&gt;CourierInbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findViewById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;R&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;courierInbox&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setOnClickMessageListener&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isRead&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;markAsRead&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;markAsUnread&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;Courier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setOnClickActionListener&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Courier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;launch&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Courier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;signIn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"pk_prod_H12..."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;clientKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YWQxN..."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"example_user_id"&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code snippet showcases the power of the Courier library in Android development, demonstrating the ability to log, mark messages as read or unread, and capture actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternative, headless approach to building an inbox with Courier
&lt;/h3&gt;

&lt;p&gt;But what if you need more control over your notification inbox's appearance and behavior? Courier also offers a headless option. This approach allows developers to customize every aspect of their inbox, tailoring it to meet specific design or functional requirements. You have the flexibility to change everything from the overall layout and individual components to the styling of each notification card. This way, Courier ensures that developers can create an inbox that not only fits their needs but also aligns perfectly with their application's UI and UX.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Whether you’re working on web or mobile app development, Courier provides a comprehensive, flexible, and easily integrable solution to ensure your users are always in the know and keep coming back to your app.&lt;/p&gt;

&lt;p&gt;It’s &lt;a href="https://app.courier.com/signup"&gt;free to try&lt;/a&gt;. You can send 10,000 messages every month for no cost as you get to know the ins and outs of what’s possible with Inbox. To help you get started, we suggest exploring the detailed guides for each platform: &lt;a href="https://www.courier.com/docs/inbox/mobile/ios-sdk/"&gt;iOS&lt;/a&gt;, &lt;a href="https://www.courier.com/docs/inbox/mobile/android-sdk/"&gt;Android&lt;/a&gt;, and &lt;a href="https://www.courier.com/docs/inbox/web/javascript-sdk/"&gt;web&lt;/a&gt;. These comprehensive guides provide step-by-step instructions and code examples to guide you through the integration process.&lt;/p&gt;

&lt;p&gt;Don't forget, Courier also provides seamless integration with push notifications. We've previously covered how to &lt;a href="https://www.courier.com/blog/android-push-notifications-with-firebase-courier-sdk/"&gt;set up push notifications with Courier&lt;/a&gt;. By combining these two  powerful features, you can provide a complete, personalized notification experience to your users.&lt;/p&gt;

&lt;p&gt;Remember, providing a top-notch notification experience to your users doesn't have to be a daunting task. If you want to build a custom notification inbox that is not only user-friendly and efficient but also easily integrable across platforms, &lt;a href="https://app.courier.com/signup"&gt;start your notifications journey&lt;/a&gt; with Courier today!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How We Built Our Documentation on Docusaurus</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Fri, 20 May 2022 22:53:32 +0000</pubDate>
      <link>https://forem.com/courier/how-we-built-our-documentation-on-docusaurus-59aj</link>
      <guid>https://forem.com/courier/how-we-built-our-documentation-on-docusaurus-59aj</guid>
      <description>&lt;p&gt;It requires an ample amount of time, effort, and resources to produce great, user-friendly documentation. Good documentation is fundamental to the way engineers get started with a product and ultimately use it in the long run, which makes it essential to both user retention and growth. Building our documentation at &lt;a href="https://www.courier.com/"&gt;Courier&lt;/a&gt; was a task we therefore took very seriously.&lt;/p&gt;

&lt;p&gt;We originally created our documentation for a very small, specific audience with specific uses for Courier. Over time, however, our user base, their use cases, and our product itself has grown dramatically. To cover our bases, we needed to improve and expand our documentation and this time, we wanted to make it scalable and with a focus on a great user experience. We decided to use &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt; to do so, which allowed our engineers to collaborate and update our documentation more efficiently. Here’s how we built our documentation and what we learned in the process that might be useful to you. &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/z7iqk1q8njt4/5rCidPNvia7pzkFHRThDWg/0eee977765165d61b895bf2aac459ef0/built-our-docs-1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/z7iqk1q8njt4/5rCidPNvia7pzkFHRThDWg/0eee977765165d61b895bf2aac459ef0/built-our-docs-1.png" alt="built-our-docs-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifying challenges with our documentation process
&lt;/h2&gt;

&lt;p&gt;When we first started Courier, we just wanted &lt;a href="https://www.courier.com/docs/"&gt;documentation&lt;/a&gt; that explained how to use Courier as a product. So, we found a tool that could help with that: ReadMe. &lt;/p&gt;

&lt;p&gt;ReadMe gave us enough features to get started with our documentation. We could create and update our documentation when needed and without much effort, but we couldn’t collaborate as much as we needed and could only save one draft at a time. This was a major challenge because anyone could easily overwrite another’s work if they were working on the same file at the same time. ReadMe also had a flat file structure which was inconvenient to use. There was also no real &lt;a href="https://git-scm.com/"&gt;Git&lt;/a&gt; integration into their CLI, so we had issues with document versioning and collaboration. Ultimately, this meant that docs were in the approval process for an unnecessary length of time and not everyone was able to make suggestions for improvement as they came up.&lt;/p&gt;

&lt;p&gt;So some years and a few significant product updates down the line, we knew that it was time for a major documentation update. ReadMe was easy to get started with and served us well for our first couple of years, but we needed something that addressed our more specific pain points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites for a documentation tool
&lt;/h2&gt;

&lt;p&gt;So we knew we needed something new. The next step was to decide which solutions were necessary to address our pain points for the next version of our documentation. The team came up with a list of requirements for documentation tools we would use in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitless Collaboration
&lt;/h3&gt;

&lt;p&gt;A happy challenge with a growing company is the need to scale almost every aspect of it as the team and product grow. As Courier’s technical team grows, we have more subject matter experts to provide input on documentation, which is good news for the quality of docs. However, more SMEs mean that we need more people to be able to collaborate on a draft at a time. Since the team will only continue to grow from here, limitless collaboration was one of the basic necessities for our future docs tool(s).&lt;/p&gt;

&lt;h3&gt;
  
  
  Versioning
&lt;/h3&gt;

&lt;p&gt;A growing company comes with the inevitability of a growing product. As new versions of the product are released, there will always be an overlap time when some of our users are using an older version of the product and some are on the newest version. Our documentation needs to cater to both of these groups of users, which means to make sure everyone has the correct information, versioning is an important docs feature. For example, we need to keep the documentation of our API 1.0.0 version separate from the one for API 2.0.0 and ensure that it is possible to update and switch between versions quickly and easily.&lt;/p&gt;

&lt;p&gt;Version was also important to allow for the option to separate and store drafts away from the official documentation so that the team would be comfortable working without the pressure of a breaking change. &lt;/p&gt;

&lt;h3&gt;
  
  
  Link Validation
&lt;/h3&gt;

&lt;p&gt;One of the worst things that can happen to user experience in product documentation is for a user to click on a link and get a 404 error. It makes it more difficult to keep users in the docs and following instructions, which can ultimately affect how many users successfully get started with the product and/or are able to troubleshoot on their own. A docs tool that would include link validation for internal links would go a long way toward avoiding damaging errors and UX issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Localization
&lt;/h3&gt;

&lt;p&gt;We have customers from different ethnic groups all around the world who speak a diverse set of languages. To cater to a global audience, users need to be able to read our documentation and get the help they need, regardless of the language they speak. Localizing our documentation like link validation, improves user retention and UX.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docusaurus matched our list
&lt;/h2&gt;

&lt;p&gt;With our list in place, we were able to begin researching to find the right new tool. After some research, we discovered that &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt;, which is built on React, allowed for limitless collaboration, versioning, link validation, and localization. &lt;/p&gt;

&lt;p&gt;With Docusaurus, we built our docs as markdowns and exported them as a static site. We created reusable custom components and now have complete control over how we build our documentation. The &lt;a href="https://mdxjs.com/"&gt;MDX&lt;/a&gt; integration is also particularly useful, as it allows any JavaScript code to be embedded into the .md file. &lt;/p&gt;

&lt;p&gt;With a few lines of code, we could tweak our theme to suit our brand colors or customize any part of our documentation by &lt;a href="https://docusaurus.io/docs/swizzling"&gt;swizzling&lt;/a&gt; the components we wanted. We also could use plugins to extend the functionality we got from Docusaurus. An example is the plugin for our search feature that allows customers to find relevant documentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/z7iqk1q8njt4/7umSHw0duZhot5KinxvX5g/1cf7082bd4060524f991dda6f714814b/built-our-docs-2.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/z7iqk1q8njt4/7umSHw0duZhot5KinxvX5g/1cf7082bd4060524f991dda6f714814b/built-our-docs-2.png" alt="built-our-docs-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also built our custom API explorer myself, which enables developers to try out our API methods directly from our docs. I built it as an embedded React component placed in the MDX file. Because we host our documentation on &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt;, we could utilize Vercel Functions to proxy the API Explorer requests to the Courier API through our docs backend endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/z7iqk1q8njt4/5UGnlKtqc5VETHZdpcSUO6/270125cefaca0baf3b7d712a4120c059/built-our-docs-3.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/z7iqk1q8njt4/5UGnlKtqc5VETHZdpcSUO6/270125cefaca0baf3b7d712a4120c059/built-our-docs-3.png" alt="built-our-docs-3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Docusaurus, we have been able to build our documentation quickly and easily. We can now make updates by simply cloning the repo on &lt;a href="https://github.com/"&gt;GitHub&lt;/a&gt;, branching off our main branch, implementing the changes, and creating a pull request (PR). Once someone has reviewed and approved the PR, it’s merged with the main branch, which deploys to Vercel and updates our site with the changes.&lt;/p&gt;

&lt;p&gt;All we need to contribute to our documentation is a GitHub account and an integrated development environment (IDE). This helps us keep track of every maintenance task for our documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next for our documentation?
&lt;/h2&gt;

&lt;p&gt;There are a few more goals we have for our documentation. We have yet to utilize Docusaurus’s localization feature, so that’s in the works. We have also recently made our documentation &lt;a href="https://github.com/trycourier/docs"&gt;open-source&lt;/a&gt; to welcome external contributors. This way, the user can take ownership of the updates they want and help themselves and others who would benefit from their contribution.&lt;/p&gt;

&lt;p&gt;Until then, learn to use Courier via our &lt;a href="https://www.courier.com/docs/guides/tutorials/how-to-design-a-notification-template/"&gt;documentation and tutorials&lt;/a&gt;. We also &lt;a href="https://www.twitch.tv/trycourier"&gt;live stream&lt;/a&gt; content that’ll help you get &lt;a href="https://www.youtube.com/channel/UCuONBIOzl-hypZ5qqWKDeeg"&gt;started and continue with Courier&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Part 4 - Observability and Analytics: The Developer's Guide to Building Notification Systems</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Wed, 15 Dec 2021 21:25:00 +0000</pubDate>
      <link>https://forem.com/courier/the-developers-guide-to-building-notification-systems-observability-and-analytics-3ioj</link>
      <guid>https://forem.com/courier/the-developers-guide-to-building-notification-systems-observability-and-analytics-3ioj</guid>
      <description>&lt;p&gt;At your CTO’s request, you recently started researching how to go about revamping or building your product’s notification system. You realized the complexity of this project around the same time as you discovered that there’s not a lot of information online on how to do it. Companies like LinkedIn, Uber, and Slack have large teams working just on notifications, but smaller companies like yours don’t have that luxury. So how can you meet the same level of quality with a team of one? This is the fourth and final post in our series on how you, the developer, can build or improve your company’s notification system. It follows the first post about identifying user requirements, the second about designing with scalability and reliability in mind, and the third about setting up routing and preferences. In this piece, we will learn about using observability and analytics to set your system and company up for success.&lt;/p&gt;

&lt;p&gt;Developing an application can often feel like you're building in the dark. Even after development, gathering and organizing performance data is invaluable for ongoing maintenance. This is where observability comes in—it’s the ability to monitor your application’s operation and &lt;em&gt;understand&lt;/em&gt; what it’s doing. With close monitoring, observability is a superpower that allows developers to use various data points to foresee potential errors or outages and make informed decisions to prevent these from occurring.&lt;br&gt;
As you build your product, consider the implications of having complete observability built into your notification system. As a developer, you’ll need to identify and quickly resolve issues by understanding how your product is performing. In the bigger picture, observability ties your technological infrastructure to your overarching product and business objectives. These key insights will also help to scale the product and manage data as your business grows. &lt;/p&gt;

&lt;h2&gt;
  
  
  Observability Use Cases
&lt;/h2&gt;

&lt;p&gt;You’re here because you want to build an application with a powerful notification system that can rival those of existing products. In this guide, you’ll learn why observability and building strong monitoring mechanisms are crucial. Here are four core observability use cases.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Track and use logs for debugging&lt;/li&gt;
&lt;li&gt;Improve customer service and experience&lt;/li&gt;
&lt;li&gt;Holistic view of your product&lt;/li&gt;
&lt;li&gt;Analytics for business development&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Logs for debugging
&lt;/h3&gt;

&lt;p&gt;Telemetry logs are the backbone of an observability system. The more infrastructure you have, the more data there will be from each instrument and service. You need to be able to understand this data. Logs can provide additional context that allows developers to determine where or why certain issues might be occurring, and effectively, how to fix them. For example, if every API request to your notification system results in a specifically formatted log line, it becomes possible to scan those log lines for anomalies. Event logs of particular actions occurring in the system, like privileged access or settings changes, can sometimes shed light on unpredicted behaviors in the system. At the very least, you should have some kind of safety net or global catch to notify you when errors creep up. &lt;/p&gt;

&lt;p&gt;When users are unable to receive notifications, you can use various logs to determine what factor(s) prevented that notification from going through. If, for example, your app is unable to deliver messages to 20% of your users, logs would reveal that those same 20% of users are using your application on a specific device type. You’ll know right away that your application has a bug that prevents it from functioning properly on that device type and act accordingly. You can also update your system to prevent this issue from occurring for future users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer service
&lt;/h3&gt;

&lt;p&gt;Let’s say a user has contacted you to report that they have unsubscribed from emails but is still receiving them. Your customer care team should be able to note relevant logs or errors, communicate with the user to ensure a good relationship, and also feed that information to the developer team for potential resolution. &lt;/p&gt;

&lt;p&gt;Observability can also help you improve overall user experience, present and future. As you consider important data points to observe within your system, think about what might impact your product, &lt;em&gt;specifically&lt;/em&gt; through the lens of your customers. If you can see some metric that might impact user experience, work to improve it before it’s an issue. For example, do your time-sensitive notifications get delivered as quickly as they should? Are all messages being delivered only once? If you’re using multiple channels, are messages being routed correctly?&lt;/p&gt;

&lt;p&gt;Additionally, if you’re using a third-party provider like SendGrid or AWS SES, you should absolutely observe connection health. If there’s a provider issue, you could notify your customers, such as through a status page, that notifications might not be working optimally. You might not be able to control the operational status of your providers, but you can still take action to maintain your customers’ trust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Holistic view of the product
&lt;/h3&gt;

&lt;p&gt;A proper observability environment should provide you with a holistic perspective of your application’s state. Based on usage and performance, you can clearly reason about how certain factors might affect your product. You can gather a real understanding of the rhythm of your notification system. You might see that your users are receiving fewer notifications at specific times in the day or year. How do you know if this is due to your application sending fewer notifications, or because they’re not getting delivered at all? With notifications, data can fluctuate drastically. Peaks in error rates can be cause for concern and require immediate engineering support, while fluctuations in send volume can be observed with caution to ensure that the application is sending the right amount of notifications.&lt;/p&gt;

&lt;p&gt;As you set up your observability, try to expose the data through user-friendly dashboards and interfaces, such as those that Datadog and Honeycomb offer. All of the collected data should be organized to provide clear insights on the application’s behaviors. Proper data visualizations are invaluable and should be tailored to more than just developer teams. For the customer service team, it is helpful to understand a specific user’s experience when something goes wrong. Likewise, commercial or marketing teams can glean insights for business development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analytics and business development
&lt;/h3&gt;

&lt;p&gt;If your users trust that you can monitor your application efficiently and resolve issues quickly or even use data to prevent their incidence, you’ll build a strong foundation and customer base. How you organize your observability system should ideally connect to your service-level metrics, which should be recorded in your SLAs (service-level agreements) that you have with your users. The observability data will be more relevant to your business if it is connected to your SLIs (service-level indicators) and SLOs (service-level objectives). An observability system that monitors all varieties of resource consumption without this connection to user experience might not foster the type of growth you want. &lt;/p&gt;

&lt;p&gt;Tracking and analyzing data on how your users are interacting with your notifications can help drive business development opportunities. Link tracking, for one, is a core observability component within a notification system. Did the user click on your notification? When and how many times?&lt;/p&gt;

&lt;p&gt;Analyzing your observability data, and especially what you do with the resulting insights, is vital to further business growth. Your observability metrics will allow you to determine if the product is meeting business expectations. For instance, you can use observability data to make scaling decisions. If you want to scale, how might increases in volume affect your system? As you aim to understand the signals from the noise in all of your observable data, every signal you find needs to drive meaningful change. This is where you will find opportunities for further development. &lt;/p&gt;

&lt;h2&gt;
  
  
  Making Your Notification System Observable
&lt;/h2&gt;

&lt;p&gt;Once you know how to use data to effectively monitor your application and make informed decisions, where do you start? The ultimate goal is to design your observability environment so that it is able to understand data, compare the data between various channels and infrastructure, and make it actionable.&lt;/p&gt;

&lt;p&gt;There’s a way to structure data to make it more useful to engineers, customer service, and business development teams. Making sense of this data requires two key measures: the &lt;em&gt;correlating&lt;/em&gt; and &lt;em&gt;normalizing events data&lt;/em&gt;.&lt;br&gt;
Correlation illustrates how different events are connected to each other and how they are connected to different users. In a notification system, this means that every outbound message, the receipt and opening of a message, and every click on the notification are measured in the ways they are statistically related to one another.&lt;/p&gt;

&lt;p&gt;Normalization refers to how we understand data points from different sources or channels and record them in a way that makes them comparable. That means re-scaling the data so that it all varies on a &lt;em&gt;similar scale&lt;/em&gt;. For example, how would data from email notifications from SendGrid compare to SMS notification data from Twilio? These are not only different companies, but also entirely different channels. &lt;/p&gt;

&lt;p&gt;Correlation and normalization of data allow you to have a more complete understanding of how your product’s notifications are performing, both in general and in relation to one another. You would then be able to filter through data and for example, find all events related to one user. This would include all email, SMS, direct message, and push notifications regardless of how and where the user received them. You can also filter through the data to find all emails sent out on a specific date under certain conditions, like for multiple users or in specific regions. &lt;br&gt;
Ultimately, if you’re working with several providers for your notification system, correlating and normalizing data points will be a vital step to achieving observability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability tips for serverless notification systems
&lt;/h2&gt;

&lt;p&gt;Your setup for an observability system will depend on your tech stack. For the sake of this example of a serverless notification system, we’ll use AWS DynamoDB and Lambda. Generally, observability boils down to metrics, logs, and traces—and how you manage them.&lt;/p&gt;

&lt;p&gt;Since we’re using AWS DynamoDB and Lambda, &lt;a href="https://aws.amazon.com/cloudwatch/"&gt;AWS CloudWatch&lt;/a&gt; is a great tool that provides built-in monitoring in connection with many other AWS services. CloudWatch collects both custom logs and those of other AWS services, as well as infrastructure metrics. &lt;/p&gt;

&lt;p&gt;At Courier we import these metrics and logs into &lt;a href="https://www.datadoghq.com/"&gt;Datadog&lt;/a&gt;, which aggregates everything on a dashboard. Datadog can be integrated with both &lt;a href="https://docs.datadoghq.com/integrations/amazon_dynamodb/"&gt;DynamoDB&lt;/a&gt; and &lt;a href="https://docs.datadoghq.com/serverless/installation/nodejs/?tab=datadogcli"&gt;Lambda&lt;/a&gt;, as well as hundreds of other services.&lt;/p&gt;

&lt;p&gt;In a notification system using Lambda and DynamoDB, you should monitor all default performance metrics such as the number of functions getting called, the number of rows being modified, and so forth. Some of the notification-specific metrics not already mentioned here include latency, error rates, and request rates. &lt;/p&gt;

&lt;p&gt;For DynamoDB in particular, it is valuable to monitor access patterns, such as inputs and outputs, in order to &lt;a href="https://www.courier.com/blog/scalability-and-reliability/"&gt;avoid hot keys&lt;/a&gt;. A tool like &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/contributorinsights.html"&gt;CloudWatch Contributor Insights&lt;/a&gt; can help identify and analyze these access patterns. &lt;/p&gt;

&lt;p&gt;AWS Lambda can automatically capture logs and then connect them to AWS CloudWatch with the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html"&gt;Lambda Logs API&lt;/a&gt;. Through this API, extensions can subscribe to function logs, extension logs, and the Lambda platform logs for events and errors. Datadog can import these as well. &lt;/p&gt;

&lt;p&gt;For logging DynamoDB activity, AWS offers &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/logging-using-cloudtrail.html"&gt;CloudTrail&lt;/a&gt;. All API calls for DynamoDB are captured as events. You can search through the event history, or you can &lt;a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-create-and-update-a-trail.html"&gt;create a trail&lt;/a&gt; for ongoing event delivery to an AWS S3 bucket. This allows for an extended record of events and you can integrate these logs to CloudWatch.&lt;/p&gt;

&lt;p&gt;If you’re using a tool like Datadog, you might forward your CloudWatch logs and metrics to Datadog using a &lt;a href="https://docs.datadoghq.com/logs/guide/send-aws-services-logs-with-the-datadog-lambda-function/?tab=awsconsole"&gt;Forwarder Lambda function&lt;/a&gt;. If you’re also using &lt;a href="https://aws.amazon.com/kinesis/"&gt;Kinesis&lt;/a&gt; in your tech stack to quickly process streaming data, you can use their &lt;a href="https://www.datadoghq.com/blog/stream-logs-with-kinesis-firehose-and-datadog/"&gt;Firehose delivery stream&lt;/a&gt; to forward logs to Datadog as well. &lt;/p&gt;

&lt;h3&gt;
  
  
  Important logs to track
&lt;/h3&gt;

&lt;p&gt;There are many vital metrics and logs to observe in a notification system. Some, already mentioned in this article, include the sending of notifications, receipt and opening of notifications, and any clicks on notifications. Other important ones include deliverability, open rate, and conversion rate. You’ll also want to note the channel and provider for each notification, such as Twilio for SMS. You are inherently looking for two different things. The first is how your notification system is operating so that you can resolve potential bugs and strive for improvement. The second is how successful your notifications are in engaging with your users. Any log or metric that might help you define those two components will be useful.&lt;/p&gt;

&lt;p&gt;Finally, traces can greatly help understand the context of logs or metrics. Traces track requests from beginning to end through all of the components in your tech stack. Tracing is especially key when your tech stack involves several intertwined systems, as it can help identify bottlenecks between those systems. Traces are normally integrated through logs, such as a unique ID for each request. One idea would be to attribute each operation in DynamoDB and Lambda to a specific notification that is sent by a specific user. &lt;/p&gt;

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

&lt;p&gt;If you’re building your own notification system, observability is vital for both maintaining and scaling your product. It is a preventative measure that can improve performance and health of your system. Whatever the relevant observability metrics may be for you, the data should be measurable, actionable, and meaningful. Remember that it’s what you do with your data that impacts your business the most.&lt;/p&gt;

&lt;p&gt;This piece taught us about the necessity of observability and analytics to monitor the functioning and performance of your in-house notifications system, as well as the advantages it provides for its future ability to scale. This is the last post in this series about how to build your own notifications. Soon, we will release an eBook so that you can access all of this information together and use it as a reference as you get building. To stay in the loop about the upcoming content, subscribe below or follow us &lt;a href="https://twitter.com/trycourier?lang=en"&gt;@trycourier&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>startup</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Part 3 - Routing &amp; Preferences: The Developer's Guide To Building Notification Systems</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Thu, 18 Nov 2021 22:21:41 +0000</pubDate>
      <link>https://forem.com/courier/the-developers-guide-to-building-notification-systems-part-3-routing-preferences-3g27</link>
      <guid>https://forem.com/courier/the-developers-guide-to-building-notification-systems-part-3-routing-preferences-3g27</guid>
      <description>&lt;p&gt;Your CTO handed you a project to revamp or build your product’s notification system recently. You realized the complexity of this project around the same time as you discovered that there’s not a lot of information online on how to do it. Companies like LinkedIn, Uber, and Slack have large teams of over 25 employees working just on notifications, but smaller companies like yours don’t have that luxury. So how can you meet the same level of quality with a team of one? This is the third post in our series on how you, a developer, can build or improve the best notification system for your company. It follows the first post about identifying &lt;a href="https://www.courier.com/blog/the-developers-guide-user-requirements"&gt;user requirements&lt;/a&gt; and designing with &lt;a href="https://www.courier.com/blog/scalability-and-reliability"&gt;scalability and reliability&lt;/a&gt; in mind. In this piece, we will learn about setting up routing and preferences.&lt;/p&gt;

&lt;p&gt;Notifications serve a range of purposes, from delivering news to providing crucial security alerts that require immediate attention. A reliable notification system both enables valuable interactions between an organization and its customers and prospects and also drives user engagement. These systems combine software engineering with the art of marketing to the right people at the right time.&lt;/p&gt;

&lt;p&gt;Building a service capable of dynamically routing notifications and managing preferences is vital to any notification system. But if you’ve never built a system like this, it might be difficult to figure out what the requirements are and where the edge cases lie. &lt;/p&gt;

&lt;p&gt;In this article, you’ll learn invaluable points to consider when building your own routing service.  You’ll understand the requirements for multi-channel support and in choosing the right API providers. You’ll also learn how to design user preferences so that you can make the most out of each message. &lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-channel support: a necessity
&lt;/h2&gt;

&lt;p&gt;Let’s say that you have just built a web-based application. The first channel that you’ll use to connect with your users is likely email because of how ubiquitous it is. However, with the diversification of channels and depending on your use case, email might not be the most efficient notification channel for you. Compared to other channels, emails typically have a low delivery rate, a low open rate, and a high time to open rate. It’s not uncommon for people to take a full day to even notice your email. If your email gets to the user, it might take awhile before they open it, if at all. &lt;/p&gt;

&lt;p&gt;To engage with your users more effectively, you’ll want to support channels across a broad range of systems not limited to any one application or device. It’s vital to understand not only which channels are most relevant for you but also for your users. If you opt to use Telegram and your users don’t have it, it won’t be a very useful channel to interact with them. Multi-channel support is also vital because while you might pick appropriate channels today, you won’t know which channels you will need to support in the future. Typically, the more appropriate channels you support, the higher the chances of intersecting with applications your users actually use now and in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing notification channels and providers
&lt;/h2&gt;

&lt;p&gt;You’ll have to select relevant channels and appropriate providers for each channel. For example, two core providers for mobile push notifications are &lt;a href="https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1"&gt;Apple Push Notification Service (APNs)&lt;/a&gt; and &lt;a href="https://firebase.google.com/docs/cloud-messaging"&gt;Firebase Cloud Messaging (FCM)&lt;/a&gt;. APNs only supports Apple devices while Firebase supports both Android and iOS as well as Chrome web apps. &lt;/p&gt;

&lt;p&gt;In the world of email providers, SendGrid, Mailgun, and Postmark are all popular but there are hundreds more. All email APIs differ in what they offer, both in supported functionality and API flexibility. Some providers, like Mailgun, only support transactional emails triggered by user activity. Other providers, like SendGrid and Sendinblue, offer both transactional and marketing emails. If your company opts for a provider that can handle both, you’ll still want to separate the traffic sources, by using different email addresses or domains, to aid email deliverability. If you only have one domain for sending both types of emails and the domain gets flagged as spam, your critical transactional emails will also be affected. Whichever provider you choose, you’ll still want to meticulously verify your DKIM, SPF, and DMARC checks, and domain and IP blacklisting using your own tools or a site like &lt;a href="https://www.mail-tester.com/"&gt;Mail-Tester&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Making requests and receiving responses also differs with each email API provider. Some providers, like Amazon SES, require the developer to &lt;a href="https://www.courier.com/blog/send-email-attachments-aws-s3"&gt;handle sending attachments&lt;/a&gt;, while others, like Mailgun, &lt;a href="https://documentation.mailgun.com/en/latest/api-sending.html#sending"&gt;provide fields in the API schema&lt;/a&gt; for including attachment files directly. &lt;/p&gt;

&lt;p&gt;There are minute variances in formatting HTTPS requests. The maximum payload sizes range from 10MB with Amazon SES API and up to 50MB with Postmark. There are also differences between the rate limits for requests.&lt;/p&gt;

&lt;p&gt;In terms of API responses, Amazon SES &lt;a href="https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html"&gt;provides&lt;/a&gt; a message identifier when an email is sent successfully through the API, but, for example, SendGrid &lt;a href="https://docs.sendgrid.com/api-reference/mail-send/mail-send"&gt;returns an empty response&lt;/a&gt; in that situation. The HTTP response codes also differ slightly depending on the provider. For example, AWS SES uses the response code &lt;a href="https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html#API_SendEmail_Errors"&gt;200&lt;/a&gt; for successful email send operations, while Sendinblue uses &lt;a href="https://developers.sendinblue.com/reference/sendtransacemail"&gt;201&lt;/a&gt;, and SendGrid uses &lt;a href="https://docs.sendgrid.com/api-reference/mail-send/mail-send#responses"&gt;202&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No matter which provider you end up choosing, don’t build your application solely to fit &lt;em&gt;their&lt;/em&gt; logic and specifications. If you do so, it will be much more difficult to change providers in the future as you’ll have to overhaul your backend. It’s crucial to invest in a layer of abstraction based on your own paradigm. &lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamically routing notifications between channels
&lt;/h2&gt;

&lt;p&gt;How do you determine which channels to use and when? Just because you’re able to use email, SMS and mobile push doesn’t mean that you should use all of them simultaneously, since doing so carries a high risk of annoying your users. This is where you begin to formulate an algorithm to route messages between the different channels and the different providers within each channel. The algorithm needs to be robust to handle delivery failures and other errors. For example, if the user hasn’t engaged with a push notification after a day, do you resend it or use email instead? &lt;/p&gt;

&lt;p&gt;You can begin constructing the algorithm using basic criteria. For example, if there is no phone number, eliminate SMS as an option for that user. If email is the primary channel, opting to send at 10 a.m. or 1 p.m. local time typically improves read rates. If the user is present or active in the app, consider sending an in-app push notification instead of an email. Finally, and especially important, get your user’s preferences for how and when they want to be contacted and integrate these preferences into your routing service. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding user preferences to your system
&lt;/h2&gt;

&lt;p&gt;Once you’ve got your channels, providers, and routing algorithm figured out, you need to think about providing users with granular control over notification preferences instead of just a binary opt-in/opt-out switch.&lt;/p&gt;

&lt;p&gt;Consider this: if you only allow opting in to or out of all notifications at once, your users might unsubscribe from all your communications because they find one specific notification annoying. As a result, you will lose out on valuable user engagement.&lt;/p&gt;

&lt;p&gt;With granular control over preferences, a user identifies exactly how and when they hear from you. If a user doesn’t like email but wants SMS messages (not common, but possible!), they can adjust their preferences and keep the SMS line of communication open. Every enabled notification channel is another opportunity to engage the user in a way that’s productive for them. From the end user’s perspective, it’s empowering to control how and when they are contacted.&lt;/p&gt;

&lt;p&gt;Note that for some channels, the user’s preferences should be ignored. For instance, two-factor authentication should go to SMS or mobile push regardless of the user’s preference for email. The possibility to override the default logic should be incorporated into your algorithm while you are designing your routing engine. &lt;/p&gt;

&lt;p&gt;If you want to take user engagement further, allow users to opt-in/opt-out of specific channels, frequency, timing and topics. You can allow them to set up their preferences based on time of day, frequency per period, or to specify more than one email address. You can give them the option to receive transactional, digest emails, daily newsletters, or only the critical ones. You can also allow them to redirect their notifications to another address, for example if the user is out of office. &lt;/p&gt;

&lt;p&gt;Granular preferences also extend past the dominion of developers and the user’s experience. Granularity of consent is becoming part of privacy &lt;a href="https://edpb.europa.eu/sites/default/files/files/file1/edpb_guidelines_202005_consent_en.pdf"&gt;compliance laws in Europe&lt;/a&gt; and in the &lt;a href="https://src.bna.com/MVJ?utm_source=ANT&amp;amp;utm_medium=ANP"&gt;state of California&lt;/a&gt; and might follow elsewhere in the future. Separately, granular preferences are an extremely advantageous analytical tool for the marketing team to improve brand strategy and personalization efforts. Is there a particular channel or topic that seems to be more popular? That information can be highly helpful to pivot in line with your users and grow your company. &lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for future-proof maintenance
&lt;/h2&gt;

&lt;p&gt;When you’re starting with notifications for a new product, there is nothing wrong with sticking to one channel and one provider. The most important principle to keep in mind is to design your notification system so that you can expand it in the future. You should leave the door open to include more providers when you need them. &lt;/p&gt;

&lt;p&gt;Don’t assume that &lt;strong&gt;API paradigms&lt;/strong&gt; are the same for each provider or notification type. For example, you want to send an email, and if delivery fails to send a push notification instead. But you won’t get a 400 HTTP response from the email provider in case of failure. The provider will retry your email over a couple of days. Instead, you’ll want to include &lt;a href="https://docs.github.com/en/developers/webhooks-and-events/webhooks/about-webhooks"&gt;webhooks&lt;/a&gt; or queues to notify you of the failure, and you’ll need to track the state of the message here. If you make blanket assumptions about how API calls work or how errors are returned, you’ll have trouble adapting to a different paradigm in the future. Instead, you can add a layer of abstraction on top of the API.&lt;/p&gt;

&lt;p&gt;It’s also invaluable to &lt;strong&gt;centralize the way you call the provider APIs&lt;/strong&gt;. If you spread out calls to an API throughout your code base, it will be more difficult to integrate other channels or API providers in the future. Let’s say you’re starting with email and &lt;a href="https://docs.courier.com/docs/setup-email-using-aws-ses"&gt;AWS SES&lt;/a&gt; as the provider. In two years’ time, you might decide to integrate mobile push notifications as well. What might that look like? The incurred technical debt will include scouring the code base for all instances of calls to the AWS SES API before you can integrate mobile push as an additional channel. But with centralized calls, you’ll have more consistent, cleaner, and reusable code as you grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many notification channels should you have?
&lt;/h2&gt;

&lt;p&gt;Typically, having three or four channels that are relevant to your product is an ideal scenario for a mature product. When you intersect channels with the preferences and availability of users, you create higher levels of complexity for your algorithm. Offering many channels for notifications might become too complex to maintain. But offering too few channels might harm your chances of interacting with users since some channels might not be viable for all users. For instance, you might decide to offer email and push notifications. But if a user didn’t download your product, your interaction with them is limited only to email. &lt;/p&gt;

&lt;h2&gt;
  
  
  Best technologies for routing and preferences engines
&lt;/h2&gt;

&lt;p&gt;It ultimately pays to choose technologies that will be a good fit for your routing and preferences needs. There will be a great deal of &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Concepts"&gt;asynchronous programming&lt;/a&gt;, as the routing service will often be waiting to receive responses for each function. You’ll want to pick a language or a framework that allows you to respond to async events at scale. &lt;/p&gt;

&lt;p&gt;The routing service also involves considerable state tracking, as most of the routing will depend on waiting on a response for each notification before changing state. The routing service will also need to be re-activated every time it receives a response from a provider and will need to determine if the notification was sent successfully or if it has to pursue next steps. See the example below of how a notification function’s state might be tracked.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/5VU4MymXGGHdDqPJPJtr4J/5096e7e427e0838b89969927f207f4c7/image1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/5VU4MymXGGHdDqPJPJtr4J/5096e7e427e0838b89969927f207f4c7/image1.png" alt="routing-and-preferences-rough-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At Courier, we use &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt;. Since our usage tends to come in bursts, serverless technology allows us to adjust and scale for changes in demand throughout each day as well as handle asynchronous operations efficiently. &lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t forget: compliance in notification routing
&lt;/h2&gt;

&lt;p&gt;When creating your own routing and preferences service, you will need to ensure that whichever channels you implement are fully compliant with applicable laws. For example, there are legal mandates on how users may be contacted or how they can unsubscribe from contact.&lt;/p&gt;

&lt;p&gt;For commercial email messages, the &lt;a href="https://www.ftc.gov/sites/default/files/documents/cases/2007/11/canspam.pdf"&gt;CAN-SPAM Act&lt;/a&gt; of 2003 is a federal United States law that spells out distinct rules and gives recipients a way to stop all contact. Penalties can cost as much as $16,000 per email in violation. This law also outlines requirements such as not using misleading header information or subject lines, identifying ads, and telling recipients how they can opt out of all future email from you. The opt-out process itself is strictly regulated.&lt;/p&gt;

&lt;p&gt;For SMS, the United States &lt;a href="https://www.fdic.gov/resources/supervision-and-examinations/consumer-compliance-examination-manual/documents/8/viii-5-1.pdf"&gt;Telephone Consumer Protection Act (TCPA)&lt;/a&gt; of 1991 sets forth rules against telemarketing and SMS marketing. Under this law, businesses cannot send messages to a recipient without their consent. This consent needs to be explicit and documented. The consent is also twofold: recipients need to consent to receiving SMS marketing messages and they need to consent to receiving them on their mobile device. Recipients need to be provided a description of what they are subscribing to, how many messages they should expect, a link to the terms and conditions of the privacy policy, and instructions on how to opt-out. &lt;/p&gt;

&lt;p&gt;In California especially, the &lt;a href="https://leginfo.legislature.ca.gov/faces/codes_displayText.xhtml?division=3.&amp;amp;part=4.&amp;amp;lawCode=CIV&amp;amp;title=1.81.5"&gt;California Consumer Privacy Act (CCPA)&lt;/a&gt; of 2018 provides additional rights for California residents only. These rights include the right to know which information a company has collected about them and how it’s used as well as the right to delete it or to opt-out of the sale of this information. Information that qualifies under the consumers’ right-to-know includes names, email addresses, products purchased, browsing history, geolocation information, fingerprints, and anything else that can be used to infer preferences. Should a consumer request this information, the company has to share the preceding 12 months of records, and also include sources of this information and with whom it was shared and why. In 2020, &lt;a href="https://src.bna.com/MVJ?utm_source=ANT&amp;amp;utm_medium=ANP"&gt;California Privacy Rights Act (CPRA)&lt;/a&gt; of 2020 amended the CCPA. The CRPA provides further consumer rights to limit the use and disclosure of their personal information.&lt;/p&gt;

&lt;p&gt;Other countries have their own compliance laws for businesses reaching out to leads and customers. Canada has its &lt;a href="https://laws-lois.justice.gc.ca/eng/acts/E-1.6/index.html"&gt;Anti-Spam Legislation (CASL)&lt;/a&gt;. The European Union has the &lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:32016R0679"&gt;General Data Protection Regulation (GDPR)&lt;/a&gt; which now also covers granularity of consent. The United Kingdom has its own regulations along with the GDPR, the &lt;a href="https://www.legislation.gov.uk/uksi/2003/2426/pdfs/uksi_20032426_en.pdf"&gt;Privacy and Electronic Communications Regulations (PECR)&lt;/a&gt; and &lt;a href="https://www.legislation.gov.uk/ukpga/2018/12/contents/enacted"&gt;Data Protection Act&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Compliance itself needs to be integrated at the developer level. Providers, like SendGrid, don’t know what you’re sending. It’s up to the developer to ensure that all applicable compliance laws are followed for their choice of channels. &lt;/p&gt;

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

&lt;p&gt;Building a notification system into a product is not for everyone. The process is time-consuming, complex, and expensive. The level of notification customizability and routing options you decide to implement will ultimately dictate a preference for either maximizing user engagement or optimizing cost. A startup with a product that hasn’t yet found its product-market fit has to focus on finding early customers and getting their feedback. But established companies with a proven customer base will have concerns related to more complex routing logic, future-proofing and compliance. This would require more functionality and higher maintenance costs.&lt;/p&gt;

&lt;p&gt;This piece taught us about the necessity of sending data for notifications to the right people, at the right frequency, at the right time and how this can be done through routing and customized preferences. Tune in for the next post in this series to learn about observability and analytics to monitor the functioning and performance of your in-house notifications system. To stay in the loop about the upcoming content, subscribe below or follow us &lt;a href="https://twitter.com/trycourier?lang=en"&gt;@trycourier&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>startup</category>
      <category>operations</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Part 2 - Scalability and Reliability: The Developer's Guide to Building Notification Systems</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Thu, 11 Nov 2021 20:31:37 +0000</pubDate>
      <link>https://forem.com/courier/the-developers-guide-to-building-notification-systems-scalability-and-reliability-2io7</link>
      <guid>https://forem.com/courier/the-developers-guide-to-building-notification-systems-scalability-and-reliability-2io7</guid>
      <description>&lt;p&gt;Your CTO handed you a project to revamp or build your product’s notification system recently. You realized the complexity of this project around the same time as you discovered that there’s not a lot of information online on how to do it. Companies like LinkedIn, Uber, and Slack have large teams of over 25 employees working just on notifications, but smaller companies like yours don’t have that luxury. So how can you meet the same level of quality with a team of one? This is the second post in our series on how you, a developer, can build or improve the best notification system for your company. In this piece, we will learn about scalability and reliability.&lt;/p&gt;

&lt;p&gt;The modern web-based application relies on notifications as a way of connecting a product with its users. Notification types include push, SMS, email, and direct messages. There are many helpful tools for building a notification system, but it’s no easy task, especially when reliability and scalability have to be taken into account.&lt;/p&gt;

&lt;p&gt;For a company to grow, it will eventually need to decide between the cost of building and maintaining its own system, or opting for the functionality and proven reliability of a third-party product. This is known as the classic build-vs-buy decision.&lt;/p&gt;

&lt;p&gt;While the cost of purchasing a solution may be clear, the cost of building your own can be difficult to calculate. In this guide, we cover building a scalable and reliable notification system in detail to give you an idea of the required effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scalability and reliability: the keys to success
&lt;/h2&gt;

&lt;p&gt;Scalability and reliability are two distinct, yet interrelated aspects at the core of a good notification system. You achieve reliability when your customer receives all of your notifications without errors or duplicates. This means reaching your customer consistently and on time. Scalability is where your application can handle higher notification volumes as a result of your product’s growth. &lt;/p&gt;

&lt;p&gt;It costs time and money to improve a notification system’s reliability. If you’re still looking for product-market fit, it might not make financial sense to prioritize reliability when your resources might be better allocated elsewhere.&lt;/p&gt;

&lt;p&gt;Once you find product-market fit and grow your user base, however, your notification volume will increase quickly. If you’re growing fast, you might choose to invest more into fixing other critical parts of your SaaS application instead of improving your notification system’s reliability. But you might jeopardize your product’s growth if your customers don’t receive your notifications due to errors, timeouts, or delays. If a problem-ridden notification system starts impacting the user experience, it’s just as likely to impact your bottom line.&lt;/p&gt;

&lt;p&gt;Scalability and reliability are both key considerations for any build-vs-buy decision. For example, when the feature management platform LaunchDarkly was making its own build-vs-buy decision, it had to consider its &lt;a href="https://sre.google/sre-book/service-level-objectives/"&gt;SLAs, SLOs, and SLIs&lt;/a&gt; as part of its investment in a notification system. It had recently closed its Series D funding, and substantial volume and load, compliance, reliability, and stability were all key factors in the decision-making process. LauchDarkly &lt;a href="https://www.courier.com/customers/launch-darkly"&gt;decided to go with Courier&lt;/a&gt; because the platform met LaunchDarkly’s strict scalability and security requirements, provided necessary features, and fit seamlessly into LaunchDarkly’s tech stack. &lt;/p&gt;

&lt;h3&gt;
  
  
  No scalability or reliability, no growth
&lt;/h3&gt;

&lt;p&gt;Scalability and reliability are two different aspects. But both become concerns if you want your company to keep up with a growing customer base. If you lack one or the other, you’ll likely meet problems along the way.&lt;/p&gt;

&lt;p&gt;If your notifications lack reliability, your brand’s impact stands to lose. To a product like Slack, a delayed push notification does not have much utility. In Slack’s case, timeliness is crucial to creating a real-time conversation between team members. &lt;/p&gt;

&lt;p&gt;But even if you’re not Slack, losing early users’ trust in your notification system can slow growth. Your early adopters will be unlikely to recommend your product if they don’t trust the way it works. Duplicate notifications represent another scenario that can be a turnoff for early users. Receiving duplicates of notifications frequently suggests to users that the product isn’t stable enough to use, so early adopters might hesitate to share the product with their friends or colleagues.&lt;/p&gt;

&lt;p&gt;So, what is the most common source of scalability and reliability issues in a notification system?&lt;/p&gt;

&lt;p&gt;Based on Courier’s experience, it’s the fact that &lt;strong&gt;notifications are rarely spread out evenly over time&lt;/strong&gt;. The reality of unpredictable volume spikes requires an understanding of how to scale infrastructure to handle high volumes at a reasonable cost. If a system doesn’t scale well to accommodate peaks, notifications will end up being processed and delivered beyond their relevance window. In the worst case scenario, an overwhelmed message queue can result in a system outage. In short, if you find your service growing and your notification system is not equipped to handle it, you are taking on considerable risk.&lt;/p&gt;

&lt;p&gt;Moreover, a notification application needs to be kept available to its users while its code is replaced. If you designed your application without keeping that in mind, you face the possibility of extended downtime while you work on it. Downtime means your users won’t receive notifications, and won’t be engaged with your product. Ultimately, designing your notification system to reduce downtime saves you both time and money.&lt;/p&gt;

&lt;p&gt;A system built without both scalability and reliability in its design patterns also risks frustrating and overworking your engineering team. Engineers on call risk getting burnt out if they have to constantly respond to alerts in the notification system. In addition, if the engineering team needs to repeatedly attend to notification issues, they might miss valuable product priorities like adding new features, improving user experience, and creating integrations.&lt;/p&gt;

&lt;p&gt;To build a good notification system, you need to know how to measure its reliability. Read on below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring the reliability of a notification system
&lt;/h3&gt;

&lt;p&gt;Site reliability engineering is a way to manage the operation of large software systems. The main tools in site reliability engineering are SLIs (Service-Level Indicators), SLOs (Service-Level Objectives), and SLAs (Service-Level Agreements). These are standards that form agreements between users and service providers, which specify the details of how a product is offered and the consequences if certain provisions are not met.&lt;/p&gt;

&lt;p&gt;The key component of any reliability measurement is the way your customers perceive your product. What levels of latency in your API do your users associate with an application that’s running smoothly? How long would a customer wait for the user interface to load before deciding that it’s broken? How soon should asynchronous jobs complete so that your customers can proceed with their day? SLAs, SLOs, and SLIs are tools to represent numeric answers to questions like these.&lt;/p&gt;

&lt;p&gt;An SLI is a metric that establishes the standards by which a service is to be provided to the user. A service-level indicator could be the speed of a database operation, or the size of a notification queue. These are the actual metrics that you would view in a tool like AWS CloudWatch or Datadog. The SLI, as the measurement, is what sets the basis for an SLO.&lt;/p&gt;

&lt;p&gt;A service-level objective is the summary goal that you as a provider want to attain. An example would be a specific latency of a notification endpoint, including the latencies of underlying middleware, queues, or databases. Here, you’ll especially need to understand which metrics actually matter to the customer and tailor your product objectives in that direction.&lt;/p&gt;

&lt;p&gt;The final layer is the SLA. Your service-level agreement is a legally-binding contract with your users. It is based on the SLO and the metrics provided in the SLIs. SLAs typically reflect the targets defined in the SLO layer. An example would be an endpoint being available and returning within 1 second for 99.9% of the time. If your product falls behind the target, a customer might get the right to request a refund for your service. So SLAs tie service objectives to direct financial losses when objectives aren’t met.&lt;/p&gt;

&lt;p&gt;These components all work together to provide a specific range of metrics within which your product is operating correctly. Paying close attention to SLIs and SLOs, which should be tailored to the customer, can help identify problems before your customers do. Things will go wrong, but how you respond to each situation will make a big difference.&lt;/p&gt;

&lt;p&gt;Notifications will be a fundamental part of your functionality. An example of an SLI could be the size of the notification queue, and an SLO could be the latency of processing a notification from creation until it's sent to the user. &lt;/p&gt;

&lt;p&gt;While most companies will not cover their notifications under an SLA, it still might be necessary in certain circumstances. For example, a B2B CRM application where notifications need to be used as reminders of upcoming client calls will probably include notification-related standards as part of an SLA. If your product requires coverage of notifications under an SLA, take care to ensure that your product metrics and objectives are aligned with your agreements to avoid overpromising and consequent legal issues.  &lt;/p&gt;

&lt;p&gt;The idea of having to use a provider API like Mailgun or SendGrid for sending emails, or interacting with a push notification service like Firebase Cloud Messaging for iOS and Android notifications, can be a reliability concern. If you are on the fence about how using a third-party provider would impact your reliability metrics, read on below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is using third-party provider APIs a reliability concern?
&lt;/h3&gt;

&lt;p&gt;In considering the scope of building a notification solution, you might feel reluctant to add provider APIs into the mix and therefore focus on managing all notifications in house. Instead of using a provider like SendGrid or Twilio, you might be considering setting up email or SMS infrastructure in-house.&lt;/p&gt;

&lt;p&gt;But is using provider APIs a reliability concern?&lt;/p&gt;

&lt;p&gt;Courier, for example, is an HTTP API. It is true that HTTP requests can fail due to connectivity issues, SSL errors, or unexpected delays. Perhaps the customer doesn’t receive an HTTP response to their API request at all. You can attempt to make such failures less common by only relying on services that reside within your network space, but due to the complexity of today’s networks eliminating such issues completely is not going to be possible.&lt;/p&gt;

&lt;p&gt;In our experience, the answer is not to avoid using APIs altogether but in how to create mechanisms to attenuate API request failures.&lt;/p&gt;

&lt;p&gt;At Courier we built mechanisms to avoid many HTTP API issues. For example, Courier uses idempotency keys to safely retry messages without duplicate sends to the customer. Integrating idempotency and other fault-tolerant processes is a vital part of building a reliable notification system.&lt;/p&gt;

&lt;p&gt;Now that we covered the core concepts, let’s discuss specific suggestions for building a scalable and reliable notification system for AWS users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for building a notification system on AWS
&lt;/h2&gt;

&lt;p&gt;If you’re using AWS, there are many tools to help you build a scalable notification system. DynamoDB and AWS Lambda are some of the AWS services that we use at Courier, and applications built using these services can be easily scalable and cost-effective to run, while requiring little to no upkeep.&lt;/p&gt;

&lt;p&gt;Still, you should take care to avoid performance bottlenecks even when using services like Lambda and DynamoDB. Below we’ll share some tips based on our experience using AWS services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Suggestions for scalability with DynamoDB
&lt;/h3&gt;

&lt;p&gt;How you build for scalability depends on the tools you choose, at least in terms of how they access your data. A system is scalable when it can still perform within its service level objectives even with an increase in volume. Whichever tools you decide to use, plan for a notification system that can handle sudden and drastic increases in data volume. &lt;/p&gt;

&lt;p&gt;When creating your own notifications application, it’s crucial to pay attention to access design patterns from the outset. You’ll need to understand how you’ll be accessing data before you start building the application. It might not be complicated to build a simple notifications application into your product, but problems don’t typically become apparent until later in the implementation or as you’re trying to scale.&lt;/p&gt;

&lt;p&gt;If you’re using DynamoDB, a common problem with access patterns is &lt;strong&gt;partition key structure&lt;/strong&gt;. DynamoDB uses primary keys that consist of two components: a partition key and a sort key. DynamoDB &lt;a href="https://aws.amazon.com/blogs/database/choosing-the-right-dynamodb-partition-key/"&gt;uses the partition key&lt;/a&gt; of a table to distribute the table’s data across partitions. The more evenly the table’s records are distributed, the higher the overall throughput of the table will be.&lt;/p&gt;

&lt;p&gt;To determine the partition a record needs to be written to, DynamoDB runs its &lt;a href="https://en.wikipedia.org/wiki/Hash_function"&gt;hashing function&lt;/a&gt; on the record’s partition key. Based on the hashing function’s output, an item is mapped to a specific physical location in the DynamoDB system. Each DynamoDB partition has a limited amount of throughput capacity. If one of your table’s underlying partitions were to receive more reads or writes than your other partitions, the throughput of your DynamoDB table would be lower than if the load were evenly distributed. Overloading one partition while underloading the others due to too many records having the same partition key is usually referred to as the &lt;strong&gt;hot key problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/s03P2aJKhbpX4oqT1cKnZ/a5872491552d30d898bbc6c81ffbad93/scalabilty-and-reliablity-1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/s03P2aJKhbpX4oqT1cKnZ/a5872491552d30d898bbc6c81ffbad93/scalabilty-and-reliablity-1.png" alt="scalabilty-and-reliablity-1"&gt;&lt;/a&gt;&lt;br&gt;
Ineffective load distribution between partitions in DynamoDB.&lt;/p&gt;

&lt;p&gt;The partitions are managed by DynamoDB itself, so the only way for a developer to address an issue with record distribution between partitions is to change the structure of the partition key. A common solution to the hot key problem is to &lt;strong&gt;create a composite partition key&lt;/strong&gt;. In our example above, the &lt;em&gt;tenant_id&lt;/em&gt; column is used as a partition key, and this configuration causes a performance bottleneck on Partition 1 when working with records for the tenant &lt;em&gt;tenant_1&lt;/em&gt;. To address the problem, we can create a composite partition key by combining the &lt;em&gt;tenant_id&lt;/em&gt; and &lt;em&gt;user_id&lt;/em&gt; attributes. See the impact of this change in the following illustration.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/4try1MQQL2A02uPyItSIx6/0fae2594eeb9301021c612a0c091809e/scalability-and-reliablity-2.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/4try1MQQL2A02uPyItSIx6/0fae2594eeb9301021c612a0c091809e/scalability-and-reliablity-2.png" alt="scalability-and-reliablity-2"&gt;&lt;/a&gt;&lt;br&gt;
More effective load distribution between partitions through the use of composite partition keys.&lt;/p&gt;

&lt;p&gt;In this example, the load is distributed more evenly because the records now have different partition keys.&lt;/p&gt;

&lt;p&gt;Similarly, if you’ll be using &lt;strong&gt;AWS S3&lt;/strong&gt; to store &lt;a href="https://www.courier.com/blog/send-email-attachments-aws-s3"&gt;attachments&lt;/a&gt; you send with your notifications, pay attention to your design access patterns. Improperly designed S3 bucket and key structure can cause throttling and therefore impact the performance of your application.&lt;/p&gt;

&lt;p&gt;Depending on the volume and predictability of your usage, &lt;strong&gt;reserved capacity provisioning&lt;/strong&gt; will be much cheaper than autoscaling or statistically provisioned capacity. DynamoDB’s auto scaling feature can handle unpredictable load patterns without human intervention, but autoscaling can get expensive. If you have a predictable volume of notifications, then maintaining infrastructure to service that volume will typically cost much less than having to handle unpredictable spikes. It’s even possible to mix auto-scaling with reserved capacity (see this example of &lt;a href="https://aws.amazon.com/blogs/database/amazon-dynamodb-auto-scaling-performance-and-cost-optimization-at-any-scale/"&gt;cost optimization with DynamoDB&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Finally, you need to be able to &lt;strong&gt;monitor and analyze your performance metrics&lt;/strong&gt; and general infrastructure. This is especially important for scalability since the metrics serve as indicators that can pinpoint issues or inefficiencies in your access design patterns. A good monitoring setup can also assist in ensuring security measures and legal compliance. For this, Courier uses &lt;a href="https://www.datadoghq.com/"&gt;Datadog&lt;/a&gt;, which can monitor servers, databases, and other tools. &lt;/p&gt;

&lt;p&gt;As you’re well aware, a scalable set-up for a notifications application requires serious planning before building. Since your needs will be different depending on your usage, a scalable system will have a good foundation that can accommodate higher volume without huge expense or re-building. Aim to understand your own design patterns and tools and how they can work for your application instead of against it. For example, DynamoDB is not a relational database and should not be used as such. You’ll need to design meticulously early on in the process since getting it right the first time will be invaluable to your company.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fine-tuning for reliability
&lt;/h3&gt;

&lt;p&gt;At Courier, we use AWS Lambda to run most of our notification-related code. If you’re going to be using AWS Lambda for your notifications application, it’s crucial to tune Lambda configuration to your required usage.&lt;/p&gt;

&lt;p&gt;For example, we recommend modifying default timeout values. The default timeout setting can differ significantly between various AWS services and AWS SDK programming languages. In the Node.js SDK, the timeout is 2 minutes, while it’s 60 seconds in the Python SDK and 50 seconds in the Java SDK.&lt;/p&gt;

&lt;p&gt;Incorrectly matching timeout settings to your use case can lead to unexpected behavior. If your Lambda function takes longer to run than the timeout configured in the SDK, you might run into &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-retry-timeout-sdk/"&gt;unexpected timeouts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our typical strategy is to right-size the timeout settings between Lambda functions, the AWS SDK, and other locations in your systems where timeouts can occur. The right timeout values will depend on your needs and  the ecosystem you’re working with.&lt;/p&gt;

&lt;p&gt;In addition, tuning your AWS service configurations and the AWS SDK parameters based on factors like queue visibility, numbers of retries, and polling frequency can generate a significant reliability payoff if you line the settings up in complement to each other. &lt;/p&gt;

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

&lt;p&gt;Building a notification system into a product is not for everyone. The process is time-consuming, complex, and expensive. Your particular requirements will ultimately dictate a preference for either functionality or cost. A startup with a product that hasn’t yet found its product-market fit has to focus on finding early customers and getting their feedback. But established companies with a proven customer base will have concerns related to higher volumes, stability, and compliance. This would require more functionality and higher maintenance costs.&lt;/p&gt;

&lt;p&gt;This piece taught us that scaling reliably can be hard, but despite the complexities, it can be done without sacrificing throughput for maximum reliability. Tune in for the next post in this series to learn about routing data and setting up preferences to create the best possible experience for the user getting your notifications. To stay in the loop about the upcoming content, subscribe below or follow us &lt;a href="https://twitter.com/trycourier?lang=en"&gt;@trycourier&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Author: Seth Carney&lt;/p&gt;

</description>
      <category>devops</category>
      <category>architecture</category>
      <category>discuss</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Part 1 - User Requirements: The Developer's Guide to Building Notification Systems</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Mon, 25 Oct 2021 18:05:52 +0000</pubDate>
      <link>https://forem.com/courier/the-developers-guide-to-building-notification-systems-user-requirements-203h</link>
      <guid>https://forem.com/courier/the-developers-guide-to-building-notification-systems-user-requirements-203h</guid>
      <description>&lt;p&gt;So your CTO has just handed you a project to revamp or build your product’s notification system. It seemed like a simple and straightforward project, but you started doing research and realized that not only is the process pretty complicated, there’s not a lot of information online on how to do it. After all, companies like LinkedIn, Uber, and Slack have large teams of over 25 employees working just on notifications. But smaller companies don’t have that luxury - so how can you meet the same level of quality with a team of one? &lt;/p&gt;

&lt;p&gt;This can certainly be overwhelming, which is why we’ve created a blog post series to guide you through building the best notification system for your company. This is the first post in this series, and we’re introducing you to the essential user requirements for both developers and nontechnical users of your notification system.&lt;/p&gt;

&lt;p&gt;It’s crucial that before building a notification system, you should know the requirements for your fellow developers and non-technical teammates who will be creating the notifications for your end users. Understanding these teammates’ personas will help you to build a more effective product with a better user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Notification System?
&lt;/h2&gt;

&lt;p&gt;A notification system is a collection of services (templates, provider integrations, routing logic, preferences, logging, etc.) that make it possible to quickly and easily create clear and direct communication between an app and its users. This clarity of communication generally involves a myriad of channels, including email, SMS, push notifications, etc. that allow the app to reach each user with the best possible user experience.&lt;/p&gt;

&lt;p&gt;A well-built notification system removes complexity from the process of creating each notification, which allows for a consistent experience across products and teams. This also provides a centralized hub for notifications across the organization, thus making monitoring and analytics more accessible.&lt;/p&gt;

&lt;p&gt;Depending on your company’s product, a notification system can work with different use cases. You can use a central notification system to alert your end-users about an incoming request, send messages about actions taken, inform end-users about product updates and upgrades or promotions, or even deploy account management notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Requirements for a Notification System
&lt;/h2&gt;

&lt;p&gt;A developer needs to understand the framework of the notification system so they can integrate it into other parts of the application or software. They are the ones who end up wiring up notifications for the myriad of applicable use cases, so it’s important to build the system with them in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability and Reliability
&lt;/h3&gt;

&lt;p&gt;Reliability makes it possible to avoid dropped messages. Even if many messages are coming in simultaneously or the system is at peak load, message delivery should be guaranteed. While there could be delays at peak load, you should be confident you aren’t losing messages.&lt;/p&gt;

&lt;p&gt;The system should also retry failed message deliveries by sending messages reliably over the network and try again if a message fails.&lt;/p&gt;

&lt;p&gt;An organization will need to send varying volumes of notifications at various times, so the developer using the API will not need to bother with auto-scaling the infrastructure. For example, an organization needs to send plenty of notifications when it has flash sales. At other times, during low-volume periods, it will need to send fewer notifications. The system should be able to scale up and down resource-efficiently as the volume of notifications changes and as an organization grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Abstracting Channels
&lt;/h3&gt;

&lt;p&gt;In the absence of a central notification system, inconsistency among channels like SMS, email, or push notifications are likely to become an issue whether among fellow developers, customer success, marketing, etc.&lt;/p&gt;

&lt;p&gt;You can change the notification channel provider, whether &lt;a href="https://aws.amazon.com/ses/"&gt;AWS SES&lt;/a&gt; or &lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt; for emails, in the notification service without changing the application code in any other products. Thus, the notification channels and providers will be abstracted and centralized instead of having the code sprinkled all over the application codebase. So if your company stops doing business with a particular provider, it can switch to a new provider in a few hours without impacting any other part of the notification service.&lt;/p&gt;

&lt;p&gt;The uniform interface makes development simpler for each product and team by abstracting the different notification providers (email, push, SMS, etc.) so the developer can easily switch between different providers without rewriting the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Good Documentation
&lt;/h3&gt;

&lt;p&gt;For other developers to use your notification system, you have to provide good documentation to understand how to use it. Internal documentation is an integral part of any system, as it educates and helps users to reference and know how to use your product. When building a platform for other developers, you have to provide great documentation so that they have the tools to figure out any support-related issues.&lt;/p&gt;

&lt;p&gt;Good documentation for a notification system should be an easy guide to help them get started. It should also provide a comprehensive reference for all the operations in the notification system. A developer integrating the notification system should not have to guess supported operations and parameters for the system.&lt;/p&gt;

&lt;p&gt;The documentation should also be easily discoverable. Without knowing exactly where to go in the system, the developer should search for what they want and find it easily. Documentation should be accurate, consistent, available on-demand, and up to date. It should include examples, code samples, screenshots, and tutorials for more context on the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intuitive APIs
&lt;/h3&gt;

&lt;p&gt;Technical users need to send notifications programmatically. So, a notification system must have APIs to submit notifications for delivery. These APIs must be intuitive from various platforms so that the system does not constrain the implementation of other systems. Users should call the APIs from any programming language or platform, and the API documentation should also be available on demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analytics
&lt;/h3&gt;

&lt;p&gt;Notifications communicate with a target audience—so, system operators want to measure the performance of the notification system (and the impact of the notifications themselves) and collect information that can help the organization design its notifications better.&lt;/p&gt;

&lt;h4&gt;
  
  
  Integrations to export data to a data warehouse
&lt;/h4&gt;

&lt;p&gt;The data the notification service collects is valuable but raw. So, analyzing the data further derives insights for your organization. Such analyses are often done in other systems where engagement events correlate with other data to better picture user behavior.&lt;/p&gt;

&lt;p&gt;A notification system should support data exports in both human-readable and machine-readable forms. It can also integrate with data warehousing tools and export to them directly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Interactions
&lt;/h4&gt;

&lt;p&gt;Engagement with a notification is an essential metric for businesses, so the notification system should track such engagement. Tracking engagements are usually done by tracking link clicks and push notification opens.&lt;/p&gt;

&lt;p&gt;For links, the notification system rewrites links in the notifications to go through itself. Each visit to the links logs an event in the system, then redirects to the original link. That enables the system to track clicks. The SDKs on the clients notice when a user opens a notification and record it for push notifications.&lt;/p&gt;

&lt;h4&gt;
  
  
  Operational metrics
&lt;/h4&gt;

&lt;p&gt;Information about the run-time behavior of the system is important for keeping the system running. Latency metrics and throughput metrics help to understand delays in the subsystems and the rate of notifications delivered. Queue length, together with service time and wait time (both latency metrics), can estimate delays and optimize the system further. These metrics help with capacity planning.&lt;/p&gt;

&lt;h4&gt;
  
  
  Support for Integrated Logging
&lt;/h4&gt;

&lt;p&gt;When things go wrong, the system should let users gain insight into those issues. Technical logs provide such insight. For example, the logs show that although notifications were successfully submitted to the system, they were not delivered to the downstream provider. The users now know that &lt;em&gt;it’s not us; it’s them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Technical users should see detailed technical logs for errors that occur when a notification is not sent. Another example: the developer should see that a message sent through SendGrid bailed because of an HTTP 401 error that says the API key is bad. Technical logs also show other vital details about the system’s operations. The logs can help operators diagnose problems when they occur.&lt;/p&gt;

&lt;h3&gt;
  
  
  Support for Test Environments
&lt;/h3&gt;

&lt;p&gt;A test environment allows the developer to simulate sending notifications safely. It is useful in continuous integration or staging environments where you need to run test code without sending notifications to actual customers or end-users.&lt;/p&gt;

&lt;p&gt;Supporting a test environment enables rapid application development and also gives confidence to the programmers. The programmers can write tests close enough to the notification system’s workings rather than mocking out the system in their tests.&lt;/p&gt;

&lt;p&gt;A test environment also allows the developers to experiment and try out different parameters and operations to see their results without impacting customers. Without this, every interaction with the notification system is potentially dangerous, as it may send a notification to a customer. A system that does not support a test environment delays the development pace because developers have to wait until production to try things out.&lt;/p&gt;

&lt;h3&gt;
  
  
  White Labeling
&lt;/h3&gt;

&lt;p&gt;If your company has spanned multiple products or brands, the notification system should be able to deploy notifications to other brands’ customers and change the branding and logos on the fly. White labeling makes changing over to new brands for sending notifications much easier. The newly acquired company can retain its brand image while switching to the existing company’s notification system. For example, Twilio, Segment, and SendGrid (all owned by the same company) want to send notifications to all three software and change the branding and colors on the spot, depending on the product receiving the notification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-Technical User Requirements for a Notification System
&lt;/h2&gt;

&lt;p&gt;Non-technical users are the ones who only need a smooth user interface and user experience to use the notification system. Designers, content editors, customer success and support, and your marketing team do not interface with code, so you have to build to suit their needs as well. Let’s look at some of the requirements for a non-technical user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usability
&lt;/h3&gt;

&lt;p&gt;Usability is a non-negotiable requirement because your users need it to create and send notifications seamlessly. Ensure the interface is user-friendly so they can explore the system quickly. It should also not require a lot of onboarding and training to understand the system.&lt;/p&gt;

&lt;p&gt;The users should be able to carry out their intended tasks efficiently (in the shortest sequence of steps possible). To achieve decent usability, choose task-based user interfaces over generic ones. Task-based interfaces are interfaces designed with a particular user action in mind. For example, logging: A customer support representative needs to find why a user is not receiving a notification and needs to be able to search for notifications to that user by email in the logs. The interface must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clearly differentiate different kinds of logs and show the relevant information for each one through a specific identifier (in this case, email).&lt;/li&gt;
&lt;li&gt;Allow searching for logs involving the user with the specified email address within the time period that the user stopped getting notifications. This makes it easy to jump to the relevant log information while wading through a large amount of data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Designing a Notification
&lt;/h3&gt;

&lt;p&gt;Designing a notification is the most important capability for a nontechnical user who will not be handling any code. Creating content and efficiently designing its layout and branding plays a central role in the way a customer success manager, for example, might use your notification. The manager needs to be able to rebrand a new logo or update text within an email or SMS without engineering going through a sprint cycle.&lt;/p&gt;

&lt;p&gt;In addition to creating a great UX for notification design, templates can help make it faster and simpler to design the notifications. These templates could provide a drag-and-drop editor to change content on the fly without redeploying code. A highly usable system should also provide ready-made templates and the ability for the user to to create new ones and customize them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Historical Records
&lt;/h3&gt;

&lt;p&gt;Non-technical users need to see some logs, although not as detailed as technical logs. Each log entry should contain information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the user that sent the notification&lt;/li&gt;
&lt;li&gt;the time&lt;/li&gt;
&lt;li&gt;the recipients&lt;/li&gt;
&lt;li&gt;the channels used&lt;/li&gt;
&lt;li&gt;the cost incurred if known&lt;/li&gt;
&lt;li&gt;the content of the notification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The notification system should also log notifications sent by other systems through API calls, not just those sent by human users. Besides logs that send notifications, the system should also log changes to access permissions. It is essential in notification systems that have role-based access control.&lt;/p&gt;

&lt;p&gt;It is important to know when a new user has permission to access the system. The notification system should log the particular permissions granted, the user that granted the permissions, and the user that received the permissions. The system should also log an event when it revokes a user’s access. Permissions granted to machine users, such as API keys and service accounts, fall under this category. These logs provide insight for non-technical users to understand their use of the system over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Role-Based Access Control (RBAC)
&lt;/h3&gt;

&lt;p&gt;Role-based access control is a system that grants permissions based on roles defined in the system. It makes it easier to manage access across an organization and tailor such access to the roles of employees and departments.&lt;/p&gt;

&lt;p&gt;For example, suppose we want to enforce the following rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only the marketing team can send notifications.&lt;/li&gt;
&lt;li&gt;Only the design team can change notification templates.&lt;/li&gt;
&lt;li&gt;Only heads of select departments can add new roles to the system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With RBAC, you can create three roles: notification-sender, designer, and role-editor, respectively, for each rule. Users who take on these roles can perform them, and users who don’t have them cannot perform them. Build the notification system in a way that is easy for small teams to use, and it should scale to larger teams and organizations, too, as they need it more.&lt;/p&gt;

&lt;p&gt;One important part of designing an RBAC is the ability to compose larger roles from smaller roles. For example, it lets you create roles that delegate smaller functions to subordinates while granting more permissions to team leaders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analytics
&lt;/h3&gt;

&lt;p&gt;Non-technical users need to see the collected data in an easily digestible way. The system should present data in a format that’s easy to understand so even non-technical users can grasp key insights at a glance.&lt;/p&gt;

&lt;p&gt;The system also needs to provide various views of the same information: aggregate statistics over different periods, various visualizations of the data, etc. They should answer the most common questions, such as what notification channel performs best for each type of message, by looking at a dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next in This Series?
&lt;/h2&gt;

&lt;p&gt;Understanding the needs of the different personas that will be using your notification system is foundational while building a notification system to ensure that your hard work is meeting the needs it was commissioned for. But understanding the needs of not only your fellow developers, but also nontechnical team members from customer success, marketing, etc. will make your hard work more available and scalable.&lt;/p&gt;

&lt;p&gt;Speaking of scaling, scalability and reliability are necessary to make sure your notification system stays current and does not require more rebuilds in the near future. Scaling reliably can be hard, but the next article in this series will explain how you can do it without sacrificing throughput for maximum reliability. We will delve more into the complexities of building a notification system and continue to provide a comprehensive guide along the way. To stay in the loop about the upcoming content, subscribe below or follow us &lt;a href="https://twitter.com/trycourier?lang=en"&gt;@trycourier&lt;/a&gt;! &lt;/p&gt;

</description>
      <category>startup</category>
      <category>tutorial</category>
      <category>discuss</category>
      <category>architecture</category>
    </item>
    <item>
      <title>LaunchDarkly uses Courier so customers can automate feature releases</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Wed, 15 Sep 2021 00:04:06 +0000</pubDate>
      <link>https://forem.com/courier/launchdarkly-allows-customers-to-automate-feature-releases-with-courier-2jho</link>
      <guid>https://forem.com/courier/launchdarkly-allows-customers-to-automate-feature-releases-with-courier-2jho</guid>
      <description>&lt;p&gt;Founded in 2014, LaunchDarkly is a Feature Management Platform that helps engineers use feature flags to build better products, faster. The ability to turn flags off without deploying allows users the ability to “launch darkly”.&lt;/p&gt;

&lt;p&gt;We had the opportunity to speak with Engineering Manager Lexi Ross to learn about how LaunchDarkly uses Courier’s notifications to deliver software with speed, security, and coordination and why Courier was the best option to implement them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automations and Notifications
&lt;/h2&gt;

&lt;p&gt;So why does LaunchDarkly’s engineering team care about notifications? Because they are necessary to retain customers and encourage future product adoption. The team in particular that is working with Courier owns Feature Workflows. Feature Workflows are customizable end to end flows by which customers can automate their flag releases. Engineers can use Feature Workflows to schedule and request approval for flag changes, plus more.&lt;/p&gt;

&lt;p&gt;One of the greatest values provided by Feature Workflows is the ability it gives product teams to automate complex workflows around feature releases. These workflows include use cases such as scheduling releases for a future date, requiring an approval workflow before a release goes live, and changing feature flags based on triggers from external systems.&lt;/p&gt;

&lt;p&gt;While all of these use cases allow LaunchDarkly customers to do more with the platform, it makes coordination and communication essential to success. If the proper user didn’t know their approval was needed, or the team wasn’t aware that a new release had been triggered, then confusion and loss of productivity could follow.&lt;/p&gt;

&lt;p&gt;A reliable, multi-channel notifications system that could provide a high quality user experience was essential for Feature Workflows’ success. Once the team understood this, it was time to find the best notification solution for their needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Courier?
&lt;/h2&gt;

&lt;p&gt;There were three main reasons for LaunchDarkly to consider using Courier. For one, their existing solution needed to be replaced as the need for certain functionality increased as well as security concerns. Second, there was a need for additional features and channels for notifications as the product and company scaled and their customers expected more speed and efficiency that existing solutions did not provide. And lastly, Courier fit seamlessly into LaunchDarkly’s existing technology and tool stack.&lt;/p&gt;

&lt;p&gt;The engineers at LaunchDarkly had been using Sendwithus for transactional email services and knew they needed to switch off of it sooner rather than later. There was some concern that the tool would be sunset eventually, and this added some urgency to the situation. They also needed a tool that was GDPR and SOC2 compliant in order to provide their customers with a reliable and secure service, which is an important promise they make to their customers. LaunchDarkly is in the critical infrastructure path for a lot of their customers, so it was particularly important to make sure all the tools they were using would be up to the necessary security standards.&lt;/p&gt;

&lt;p&gt;As LaunchDarkly scaled, they knew that they also needed to add certain features and channels to continuously make the product more sophisticated. Many of their customers, for example, used Slack and Microsoft teams and Courier’s existing integrations with both teams made it easier to reach both sets of users. It also needed to be possible for the company’s support teams to be able to debug any email issues, which required them to be able to access and review logs.&lt;/p&gt;

&lt;p&gt;It also helped that Courier fit so well into LaunchDarkly’s existing suite of tools. The engineers wanted to use a Go SDK, for example. As a team that builds many SDKs themselves, it was important for them to be able to allow their customers to integrate directly into their stack. Because they already used Mailbin under the hood to send emails, the team was also looking for a solution that would let them continue to work with Mailbin as a provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Road to Success
&lt;/h2&gt;

&lt;p&gt;Integrating Courier with LaunchDarkly’s existing suite was particularly easy, and in some ways, interesting as well. For example, LaunchDarkly’s engineers recently implemented Slack notifications specifically for the approvals use case. The team requires approvals for changes to their own LaunchDarkly production environment, which acted as encouragement to use Slack notifications. Since users are often using Slack all day and ubiquitously, it would make for a better user experience to be able to handle approvals directly within the tool.&lt;/p&gt;

&lt;p&gt;In order to make use of LaunchDarkly’s existing Slack integration, the engineers used the Courier webhooks channel instead of the Courier Slack channel. This worked well because they had an existing way to send webhooks to their own Slack integration. So now any time they want to send an approval notification to Slack, they use the webhook channel to send to the designated customer through any Slack instances with LaunchDarkly installed.&lt;/p&gt;

&lt;p&gt;Adding Slack as a channel definitively reduced approval time internally at LaunchDarkly and feedback from developers has also been positive. Most importantly, however, additional channels like Slack increased LaunchDarkly’s customers’ ability to operate a coordinated workflow, which is another important promise the company makes to its users.&lt;/p&gt;

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

&lt;p&gt;Overall, the folks at LaunchDarkly found that Courier was the best option for multi-channel use cases and product notifications in particular. Courier was able to facilitate LaunchDarkly’s ability to provide their customers with speed, security and coordination through Automations, security compliance, and multi-channel communications for flags. It was also an ideal tool to use for a product with a developer audience, as Courier itself was built for developers.&lt;/p&gt;

</description>
      <category>launchdarkly</category>
      <category>automation</category>
      <category>features</category>
      <category>notifications</category>
    </item>
    <item>
      <title>How I used Unity and Courier to Create a Notification-based Game</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Mon, 23 Aug 2021 18:57:40 +0000</pubDate>
      <link>https://forem.com/courier/how-i-used-unity-and-courier-to-create-a-notification-based-game-363k</link>
      <guid>https://forem.com/courier/how-i-used-unity-and-courier-to-create-a-notification-based-game-363k</guid>
      <description>&lt;p&gt;Hello, world! I’m Matt Graber. I just finished my undergrad at the University of Maryland. I started my game development career back in freshman year in the UMD AR club. I used to teach other students how to create augmented and virtual reality experiences with Unity, a cross-platform game engine. I also enjoy informal game jams and larger projects in Unity with fellow developer friends.&lt;/p&gt;

&lt;p&gt;Recently, I won the sponsored prize at the Bitcamp hackathon for building &lt;a href="https://devpost.com/software/package-person-modernizing-arcades-through-messaging"&gt;Package Person&lt;/a&gt; —an arcade game that notifies players of their status on the leaderboard. It was an exciting hackathon and also a learning experience for me. I learned how to use Courier and also how to use Unity with C# to create automatic POST requests to an API server—a teaser of what I will share later on.&lt;/p&gt;

&lt;p&gt;I got featured in &lt;a href="https://youtu.be/GRO6Ndye7rc"&gt;Courier’s live stream&lt;/a&gt; to build a notification-based game with &lt;a href="https://unity.com/"&gt;Unity Engine&lt;/a&gt;, &lt;a href="https://www.courier.com/"&gt;Courier&lt;/a&gt;, &lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt;, and &lt;a href="https://www.mailjet.com/"&gt;Mailjet&lt;/a&gt;. In this article, I will walk through the design of this game, called &lt;a href="https://github.com/grabermtw/Courier-Unity-Integration-Demo"&gt;Rain Spikes&lt;/a&gt;, and provide a step-by-step explanation of how I used Courier to integrate notifications into the game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rain Spikes: The design of the game
&lt;/h2&gt;

&lt;p&gt;People are naturally competitive. Even though Rain Spikes is simple, it’s addicting because players get notified about their score via email and text messaging, which prompts them to try it again and again and again.&lt;/p&gt;

&lt;p&gt;Once the game starts, the player uses arrow keys to move a square from left to right to dodge the falling spikes. The more spikes they dodge, the more points they gain. If a spike hits your square, the game ends with a prompt to fill in your details—name, phone number, email, and memo (a message to include in the email). When you click submit, an automated email or an SMS message is sent showing your end score and the memo. Here’s how the game works:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/2nfldv4paC9wAIHnwqwTtI/6c41e3029bb97de6a8b0f1f4cfda33be/gaming-1.gif" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/2nfldv4paC9wAIHnwqwTtI/6c41e3029bb97de6a8b0f1f4cfda33be/gaming-1.gif" alt="gaming-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s go into a little bit of the design architecture for this game. First, if you don’t know, everything in Unity is object-oriented and is based on game objects. For example, the player game object (the object the player controls) is the square. The player game object has two components attached to it: the &lt;a href="https://docs.unity3d.com/Manual/class-SpriteRenderer.html"&gt;Sprite Renderer&lt;/a&gt;, which renders 2D and 3D objects, and &lt;a href="https://docs.unity3d.com/Manual/class-BoxCollider2D.html"&gt;Box Collider 2D&lt;/a&gt;, which detects collisions (in this game, with the falling spikes).&lt;/p&gt;

&lt;p&gt;Finally, the last component I implemented as a script is the player control component, which enables the player to move the box with the left and right arrow keys independently of the frame rate.&lt;/p&gt;

&lt;p&gt;As I said earlier, I wanted to add notifications to give Rain Spikes a competitive, addicting edge. So here’s how I did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I added notifications using Courier to notify players of their score
&lt;/h2&gt;

&lt;p&gt;Courier’s API allowed me to use the information collected in the end-game form to send notifications whenever the game ends. Of course, the emails and text messages are opt-in, of course—nobody wants to design an app that spams people with unwanted emails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; I created a basic game with Unity Engine. While this article focuses on my Courier integration into the game, here is a recommended beginner-friendly tutorial to create a game with Unity Engine. Feel free to check out my code for this game&lt;br&gt;
on &lt;a href="https://github.com/grabermtw/Courier-Unity-Integration-Demo/tree/main"&gt;GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; I already had an account on Courier, so all I needed was to login and click on create a notification. If you don’t have a Courier account, it’s a simple sign-up.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/3SeGIqLBuwqODS3i5dWaZg/cdaee1977002b6125f5bb9573cb5a850/gaming-2.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/3SeGIqLBuwqODS3i5dWaZg/cdaee1977002b6125f5bb9573cb5a850/gaming-2.png" alt="gaming-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Courier has different channels to deploy notifications from the exact location. So all I had to do was choose the specific channels for this game: &lt;a href="https://dev.mailjet.com/email/guides/"&gt;Mailjet for emails&lt;/a&gt; and &lt;a href="https://www.twilio.com/docs/sms"&gt;Twilio for SMS messages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/5kj2z48n8SRTsemyMCcsOF/155931c35a0a5aa81374d46bd710f7d4/gaming-3.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/5kj2z48n8SRTsemyMCcsOF/155931c35a0a5aa81374d46bd710f7d4/gaming-3.png" alt="gaming-3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Next, I had to write out the email message for Mailjet:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello {playerName},
You recently scored {score} while playing that Courier-Unity integration demo game!
You sent the following memo to yourself:
{memo}
You should play again sometime!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;playerName, score, and memo are assigned variables to fetch data from the game. So by assigning these variables, I sent values from the game to display as part of the message. For the memo and score variable, I made them conditionals—if the memo is not empty, the memo will be displayed. So these fields will only be displayed if the player actually submitted a memo or has a score. Adding conditionals on Courier is a lot easier than figuring out all the logic on the C# side.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/57EaEdxwy3rDpjANmq3XLb/0bc056b9f92bfbf17967b2012d561573/gaming-4.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/57EaEdxwy3rDpjANmq3XLb/0bc056b9f92bfbf17967b2012d561573/gaming-4.png" alt="gaming-4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; The next thing was to replicate the same message for Twilio. In Courier, it is easy to drag and drop the text because they are shared components. Thus, it saves the time of typing everything out again.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/3hCMNo8DYLnRYVMVBHvSPP/00ef22634216b35c4d83d7faa987dc12/gaming-5.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/3hCMNo8DYLnRYVMVBHvSPP/00ef22634216b35c4d83d7faa987dc12/gaming-5.png" alt="gaming-5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6:&lt;/strong&gt; After adding the messages, I went over to preview and wrote a test event, and checked if the notifications were working. The test event contains the assigned variables—playerName, score, email, phone number—and test values.&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="p"&gt;{&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playerName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Billy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;score&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;memo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello billy here&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profile&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dummyemail@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;phone_number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;555555555&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;override&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;&lt;a href="//images.contentful.com/z7iqk1q8njt4/hPtJKeDeBCT94ojbPXJ0b/e119872d7ba688b8206c432cd15e5c67/gaming-6.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/hPtJKeDeBCT94ojbPXJ0b/e119872d7ba688b8206c432cd15e5c67/gaming-6.png" alt="gaming-6"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7:&lt;/strong&gt; After previewing the notifications and testing everything was working, I published changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/5fdp04XgHIRlKiitIMJDls/e40643848dab9c9ed98faf2204a28232/gaming-7.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/5fdp04XgHIRlKiitIMJDls/e40643848dab9c9ed98faf2204a28232/gaming-7.png" alt="gaming-7"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8:&lt;/strong&gt; Now, this is the tricky part. Currently, Courier has SDKs for Ruby, Python, Go, Java, etc. Unfortunately, there is none for C#. These SDKs generate sample code for sending notifications to the programming languages. It was not much of an issue. All I had to do was create a web request from Unity following the curl logic on Courier.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/72h9gw7clqvCnr2Yzlg1oH/1036e6adef771356cfb3f4a36405d20b/gaming-8.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/72h9gw7clqvCnr2Yzlg1oH/1036e6adef771356cfb3f4a36405d20b/gaming-8.png" alt="gaming-8"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9:&lt;/strong&gt; Before starting the notification script code, I had to get references for each input field to fetch data to the script. In the Unity Engine inspector, the input field has a component called TextMeshPro (TMP). TMP is the &lt;a href="https://docs.unity3d.com/Manual/com.unity.textmeshpro.html"&gt;“ultimate text solution”&lt;/a&gt; in Unity Engine.     &lt;/p&gt;

&lt;p&gt;Here’s what the notification script code looks like:&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;using&lt;/span&gt; &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Collections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Collections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Networking&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SceneManagement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;TMPro&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MonoBehaviour&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;TMP_InputField&lt;/span&gt; &lt;span class="nx"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;TMP_InputField&lt;/span&gt; &lt;span class="nx"&gt;emailInput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;TMP_InputField&lt;/span&gt; &lt;span class="nx"&gt;phoneInput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;TMP_InputField&lt;/span&gt; &lt;span class="nx"&gt;memoInput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;GameManager&lt;/span&gt; &lt;span class="nx"&gt;gameManager&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;string&lt;/span&gt; &lt;span class="nx"&gt;EVENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&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;string&lt;/span&gt; &lt;span class="nx"&gt;AUTH_KEY&lt;/span&gt; &lt;span class="o"&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;// Start is called before the first frame update&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;gameManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GetComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GameManager&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;Submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;StartCoroutine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SendNotification&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;IEnumerator&lt;/span&gt; &lt;span class="nx"&gt;SendNotification&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nx"&gt;WWWForm&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WWWForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
           &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EVENT_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recipient&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;override&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;playerName&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;score&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;gameManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetScore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;memo&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;memoInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;emailInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                         &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;phone_number&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;phoneInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UnityWebRequest&lt;/span&gt; &lt;span class="nx"&gt;www&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UnityWebRequest&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="s2"&gt;https://api.courier.com/send&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
           &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetRequestHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bearer &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;AUTH_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SendWebRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;UnityWebRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;Debug&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="nx"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
           &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="nx"&gt;Debug&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="s2"&gt;Form upload complete!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                 &lt;span class="c1"&gt;// reload the scene to play again&lt;/span&gt;
                 &lt;span class="nx"&gt;SceneManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LoadScene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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;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;Don’t get overwhelmed: Here’s a breakdown of the different methods and classes I used in this script.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;public class notifications:&lt;/strong&gt; public class notifications handle the input fields for name, email, phone number, and memo.The GameManger input field is private because that is where the score will be stored. In order to create this method, I imported the TextMeshPro with a &lt;code&gt;using&lt;/code&gt;statement. Importing TMP gives the flexibility to use input fields in Unity Engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;void Start:&lt;/strong&gt; void Start method handles the private GameManager reference to the game object in the Unity engine inspector.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;public void Submit:&lt;/strong&gt; The Submit method handles what happens when a player hits the submit button. It basically starts a coroutine (StartCoroutine) in order to send a message. The coroutine in Unity is responsible for executing the game logic over a number of frames. A coroutine is important because it sends web requests in Unity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;private IEnumerator SendNotifications:&lt;/strong&gt; The private IEnumerator method is the coroutine that handles all frame iterations. In this method, I used the class WWWform to construct a form that would hold all the data that will be sent to Courier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UnityWebRequest:&lt;/strong&gt; UnityWebRequest class is the class that sends the POST request(form) to Courier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SetRequestHeader:&lt;/strong&gt; For the request to work, I had to set a request header to handle the authorization and authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;yield return:&lt;/strong&gt; yield return handles an async operation (POST request to Courier API) and pauses the execution until the request is sent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug.Log:&lt;/strong&gt; Debug.Log is used to print to the console in Unity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SceneManager:&lt;/strong&gt; The SceneManager is responsible for reloading the scene or restarting the game once the submit request is sent.
Check out the &lt;a href="https://www.youtube.com/watch?v=GRO6Ndye7rc&amp;amp;ab_channel=Courier"&gt;Courier Live Stream&lt;/a&gt; for some additional explanation of the notifications script code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 10:&lt;/strong&gt; After coding the Notification script, I attached it to the GameManager gameObject, populated its input fields, and assigned the Submit button to be executed onClick event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 11:&lt;/strong&gt; Next, I went over to the Courier notification to map out the event ID selected in the code and get the authentication token.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/6obGHQDQ3Td4p1qiMwF4Zr/819f8cf06fd245ca675127eac35368d0/gaming-9.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/6obGHQDQ3Td4p1qiMwF4Zr/819f8cf06fd245ca675127eac35368d0/gaming-9.png" alt="gaming-9"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 12:&lt;/strong&gt; Now’s the fun part! I gave the game a test play. &lt;/p&gt;

&lt;h2&gt;
  
  
  With Courier, I integrated notifications in under an hour
&lt;/h2&gt;

&lt;p&gt;This process would have been a bona fide nightmare without Courier’s API. I would have had to program the logic myself, which would have taken hours, if not days.&lt;/p&gt;

&lt;p&gt;With Courier’s pre-built logic and conditionals for the message content, I was able to build email and text SMS notifications into the game in less than an hour. Heck, you can watch me do the entire process in the live stream.&lt;/p&gt;

&lt;p&gt;While Rain Spikes was mainly an exercise in building notifications into a game, I see a ton of potential for Courier to be used for more complex and sophisticated projects in the future. For example, notifying players when someone has achieved a special reward or my idea for modernizing &lt;a href="https://devpost.com/software/package-person-modernizing-arcades-through-messaging#:~:text=What%27s%20next%20for,environment%20and%20UI."&gt;Courier messaging in Package Person&lt;/a&gt; is to send gamers updates about the game via Discord.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Illustration by &lt;a href="http://www.rrebekkaa.com/"&gt;Rebekka Dunlap&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>unity3d</category>
      <category>notifications</category>
    </item>
    <item>
      <title>Three Ways to Send Emails Using Python With Code Tutorials</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Wed, 11 Aug 2021 20:29:12 +0000</pubDate>
      <link>https://forem.com/courier/three-ways-to-send-emails-using-python-with-code-tutorials-5bbc</link>
      <guid>https://forem.com/courier/three-ways-to-send-emails-using-python-with-code-tutorials-5bbc</guid>
      <description>&lt;p&gt;For software products of every scale, emails are the de facto standard for notifying your users. It’s a fast, cost-effective, and readily accessible channel for reaching your users, especially if you’re sending transactional emails or generating event-driven alerts. &lt;/p&gt;

&lt;p&gt;In this post, I’ll go over three ways to send emails with Python. Apps can leverage Python for sending emails for an array of use cases. For example, you can generate alarms if things go south in production, send confirmation emails to new users, or notify users of new activity in your app. &lt;/p&gt;

&lt;h2&gt;
  
  
  3 ways to send emails from your Python app
&lt;/h2&gt;

&lt;p&gt;There are three main options for sending email with Python: SMTP, a transactional email service, and a multichannel notifications service.&lt;/p&gt;

&lt;p&gt;Below, I’ll review the pros and cons for each option. Then, in the next section, I’ll walk you through three different code tutorials for using each option to send emails with Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Using SMTP
&lt;/h3&gt;

&lt;p&gt;Python has a built-in module for sending emails via SMTP, which makes getting started with email a piece of cake.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros of using SMTP
&lt;/h4&gt;

&lt;p&gt;Easy to set up&lt;/p&gt;

&lt;p&gt;Highly cost-effective&lt;/p&gt;

&lt;p&gt;Platform agnostic&lt;/p&gt;

&lt;h4&gt;
  
  
  Cons of using SMTP
&lt;/h4&gt;

&lt;p&gt;Less secure&lt;/p&gt;

&lt;p&gt;No built-in analytics&lt;/p&gt;

&lt;p&gt;Longer send times&lt;/p&gt;

&lt;p&gt;Long-term maintenance and uptime burden&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Using a transactional email service
&lt;/h3&gt;

&lt;p&gt;You can also easily integrate third-party &lt;a href="https://www.courier.com/blog/best-transactional-email-api-service" rel="noopener noreferrer"&gt;transactional email APIs&lt;/a&gt; like &lt;a href="https://sendgrid.com/" rel="noopener noreferrer"&gt;SendGrid&lt;/a&gt;, &lt;a href="https://www.mailgun.com/" rel="noopener noreferrer"&gt;Mailgun&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/ses/" rel="noopener noreferrer"&gt;AWS SES&lt;/a&gt;. If you are planning to send a high volume of emails or need to ensure deliverability, a hosted email API can be a great option and many providers offer a free or low-cost plan to start.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros of transactional email services
&lt;/h4&gt;

&lt;p&gt;Feature-rich, e.g. analytics&lt;/p&gt;

&lt;p&gt;High email delivery rates&lt;/p&gt;

&lt;p&gt;Better email delivery speeds&lt;/p&gt;

&lt;p&gt;Scalability and reliability&lt;/p&gt;

&lt;h4&gt;
  
  
  Cons of transactional email services
&lt;/h4&gt;

&lt;p&gt;Learning curve for new API&lt;/p&gt;

&lt;p&gt;Dependent on third-party intermediary&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Using a multichannel notifications service
&lt;/h3&gt;

&lt;p&gt;Finally, if you’re planning to notify users on more than one channel, you can use a multichannel notifications service. &lt;a href="https://www.courier.com/" rel="noopener noreferrer"&gt;Courier&lt;/a&gt;, for example, gives you one uniform API to notify users over email, SMS, push, and chat apps like Slack and WhatsApp. Plus, you’ll get a drag-and-drop template builder and real-time logs and analytics for all your channels.&lt;/p&gt;

&lt;p&gt;Even if you’re only sending emails today, multichannel notifications services can save you time and money. With a platform like Courier, you can easily add new channels, switch email service providers, or even add backup providers without writing any additional code. You get a complete notifications system that can scale with your product’s growth.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros of multichannel notifications services
&lt;/h4&gt;

&lt;p&gt;Single API for multiple channels&lt;/p&gt;

&lt;p&gt;Easy to manage cross-channel delivery&lt;/p&gt;

&lt;p&gt;Less code to write and maintain&lt;/p&gt;

&lt;h4&gt;
  
  
  Cons of multichannel notifications services
&lt;/h4&gt;

&lt;p&gt;Additional third-party intermediary&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: How to send emails using SMTP in Python
&lt;/h2&gt;

&lt;p&gt;You can use Python’s &lt;a href="https://docs.python.org/3/library/smtplib.html" rel="noopener noreferrer"&gt;built-in &lt;code&gt;smtplib&lt;/code&gt; module&lt;/a&gt; to send email using SMTP (Simple Mail Transfer Protocol), which is an application-level protocol. Note that the module makes use of &lt;a href="https://tools.ietf.org/html/rfc821" rel="noopener noreferrer"&gt;RFC 821&lt;/a&gt; protocol for SMTP. I’ll show you how to use Gmail’s SMTP server for this walkthrough. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Set up a Gmail account for sending your emails. Since you’ll be feeding a plaintext password to the program, Google considers the SMTP connection less secure. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the account settings and &lt;a href="https://myaccount.google.com/lesssecureapps" rel="noopener noreferrer"&gt;allow less secure apps&lt;/a&gt; to access the account. As an aside, Gmail doesn't necessarily use SMTP on their internal mail servers; however, Gmail SMTP is an interface enabled by Google's smtp.gmail.com server. You might find smtp.gmail.com in email clients like Thunderbird, Outlook, and others.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Import &lt;code&gt;smtplib&lt;/code&gt;. Since Python comes pre-packaged with &lt;code&gt;smtplib&lt;/code&gt;, all you have to do is create a Python file and import &lt;code&gt;smtplib&lt;/code&gt; into it. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To create a secure connection, you can either use &lt;code&gt;SMTP_SSL()&lt;/code&gt; with 465 port or &lt;code&gt;.starttls()&lt;/code&gt; with 587 port. The former creates an SMTP connection that is secured from the beginning. The latter creates an unsecured SMTP connection that is encrypted via &lt;code&gt;.starttls()&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To send email through &lt;code&gt;SMTP_SSL()&lt;/code&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;import&lt;/span&gt; &lt;span class="nx"&gt;smtplib&lt;/span&gt;

&lt;span class="nx"&gt;gmail_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your_email@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;gmail_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your_password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;sent_from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gmail_user&lt;/span&gt;
&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&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;person_a@gmail.com&lt;/span&gt;&lt;span class="dl"&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;person_b@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lorem ipsum dolor sit amet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;consectetur adipiscing elit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;email_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
From: %s
To: %s
Subject: %s

%s
&lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sent_from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&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="nx"&gt;smtp_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;smtplib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SMTP_SSL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;smtp.gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;465&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;smtp_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ehlo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;smtp_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gmail_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gmail_password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;smtp_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sent_from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;smtp_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email sent successfully!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;except&lt;/span&gt; &lt;span class="nx"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong….&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To send email through &lt;code&gt;.starttls()&lt;/code&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;import&lt;/span&gt; &lt;span class="nx"&gt;smtplib&lt;/span&gt; 
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;Create&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;SMTP&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; 
    &lt;span class="nx"&gt;smtp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;smtplib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SMTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;smtp.gmail.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

   &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;Use&lt;/span&gt; &lt;span class="nx"&gt;TLS&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;security&lt;/span&gt; 
    &lt;span class="nx"&gt;smtp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;starttls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="nx"&gt;Authentication&lt;/span&gt; 
    &lt;span class="nx"&gt;smtp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sender_email_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sender_email_id_password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;Defining&lt;/span&gt; &lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt; 
    &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Message_you_need_to_send&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;Sending&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;Email&lt;/span&gt;
    &lt;span class="nx"&gt;smtp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sender_email_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;receiyer_email_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;Terminating&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; 
    &lt;span class="nx"&gt;smtp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="nf"&gt;print &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email sent successfully!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;span class="nx"&gt;except&lt;/span&gt; &lt;span class="nx"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong....&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you've initiated a secured SMTP connection, you can move forward and write your message and pass to &lt;code&gt;.sendmail()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: How to send emails using a transactional email service in Python
&lt;/h2&gt;

&lt;p&gt;If you need to send a high volume of transactional emails or optimize deliverability, consider using a &lt;a href="https://www.courier.com/blog/best-transactional-email-api-service" rel="noopener noreferrer"&gt;transactional email service&lt;/a&gt;. There are many to choose from, including Amazon SES, Mailgun, and Postmark, and the vast majority support Python. &lt;/p&gt;

&lt;p&gt;In this tutorial, I’m going to use &lt;a href="https://sendgrid.com/" rel="noopener noreferrer"&gt;SendGrid&lt;/a&gt;, one of the most popular email APIs. What sets a service like SendGrid apart from SMTP are the out-of-the box features. SendGrid offers easy integration with a simple API, email analytics, round-the-clock support, and high deliverability rates.&lt;/p&gt;

&lt;p&gt;Setting up SendGrid with Python is a fairly simple process:&lt;/p&gt;

&lt;p&gt;Create an &lt;a href="https://signup.sendgrid.com/" rel="noopener noreferrer"&gt;account with SendGrid&lt;/a&gt;. SendGrid’s free plan includes 100 emails per day.&lt;/p&gt;

&lt;p&gt;Generate and store a SendGrid API key and provide full access to &lt;em&gt;Mail Send&lt;/em&gt; permissions.&lt;/p&gt;

&lt;p&gt;Create a Python script and start using the API.&lt;/p&gt;

&lt;p&gt;To begin using SendGrid’s API via Python, follow these steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;To install the &lt;code&gt;sendgrid&lt;/code&gt; package on your machine, refer to SendGrid's &lt;a href="https://github.com/sendgrid/sendgrid-python" rel="noopener noreferrer"&gt;GitHub installation guide&lt;/a&gt; or directly install via &lt;code&gt;pip install sendgrid&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To use the package in a Python script:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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="nx"&gt;sendgrid&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;sendgrid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mail&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Mail&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;To&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;To assign your API key to the SendGrid API client:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;my_sg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sendgrid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SendGridAPIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SENDGRID_API_KEY&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;ol&gt;
&lt;li&gt;To send email, create the body and generate JSON representation. Refer to SendGrid’s complete code block:
&lt;/li&gt;
&lt;/ol&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="nx"&gt;sendgrid&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;sendgrid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mail&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Mail&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;To&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Content&lt;/span&gt;

&lt;span class="nx"&gt;my_sg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sendgrid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SendGridAPIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SENDGRID_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Change&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;
&lt;span class="nx"&gt;from_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&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;your_email@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Change&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;
&lt;span class="nx"&gt;to_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;destination@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  

&lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lorem ipsum dolor sit amet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;consectetur adipiscing elit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;from_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Get&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt; &lt;span class="nx"&gt;representation&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;Mail&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;
&lt;span class="nx"&gt;mail_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Send&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;HTTP&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;
&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;my_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mail&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="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;mail_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you can easily set up SendGrid and send up to 10,000 exclusive mail requests every second with your Django and Flask web applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: How to send emails using a multi-channel notifications service in Python
&lt;/h2&gt;

&lt;p&gt;If you’re looking to scale your application’s notification capabilities while keeping your codebase clean, you should consider a multichannel notifications service like Courier. Courier allows you to bring your own email provider, including support for SMTP and most popular transactional email APIs. &lt;/p&gt;

&lt;p&gt;I’ll walk you through setting up &lt;a href="https://www.courier.com/" rel="noopener noreferrer"&gt;Courier&lt;/a&gt; and sending notifications in the following steps. I’ll use the same SendGrid account that we set up in the prior tutorial.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="http://courier.com/register" rel="noopener noreferrer"&gt;Sign up for Courier&lt;/a&gt; and navigate to the &lt;em&gt;Designer&lt;/em&gt; tab on the left.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8e5atkjof2uc33v93xt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8e5atkjof2uc33v93xt.png" alt="Courier's Designer Tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click "Create Notification". Now you’re ready to integrate a provider for email. Courier supports direct integrations with a number of popular email providers such as SendGrid, Postmark, MailChimp Transactional, and more. For this tutorial, let’s go with SendGrid.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpqp3qkft6jvga72jmjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpqp3qkft6jvga72jmjr.png" alt="Create Notification in Courier's Design Hub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To integrate with the channel of your choice, click "+ Add Channel". Once you’ve added your configured SendGrid integration, you can start adding content.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3xxtgfrfpsvgyrywiz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3xxtgfrfpsvgyrywiz5.png" alt="Add a Channel in Courier's Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;To design your notification, just drag and drop the template blocks. You can also add custom code to your notification, either by overriding the entire email or by adding a code block. If you decide to send your notification over another channel, you can reuse the same template blocks and Courier will take care of updating the formatting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With your Courier account configured, create a Python script. You can download &lt;a href="https://github.com/trycourier/courier-python" rel="noopener noreferrer"&gt;Courier’s Python Package&lt;/a&gt; via &lt;code&gt;pip install trycourier&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once you’ve published your notification, Courier will automatically generate a code snippet for you to use. Copy-paste the code snippet and make an API call with the following script:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;trycourier&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Courier&lt;/span&gt; 

&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Courier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Courier_Authentication_Token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; 
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-notification-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="nx"&gt;ID&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;Courier&lt;/span&gt; 
    &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-recipient-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;Usually&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s User ID
    profile={ 
        "email": "user@example.com" #The recipient’s email address
    }, 
    data={ 
        "Loredm Ipsum": "dolor sit amet" #Tthe message you wish to send 
    }
)

print(response[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;])
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to send emails with attachments in Python&lt;br&gt;
To include attachments with in your email notifications, you can add an optional 'override' parameter as follows:&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;from&lt;/span&gt; &lt;span class="nx"&gt;trycourier&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Courier&lt;/span&gt; 

&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Courier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Courier_Authentication_Token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; 
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-event-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-recipient-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recipient_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;phone_number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recipient_number&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="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Loredm Ipsum&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dolor sit amet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;override&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;Pass&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messageId&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;Pass the following override to the override parameter to equip your emails with attachment functionality:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;override&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="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="err"&gt;”&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="s2"&gt;attachments&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;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;filename&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sample_file.txt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contentType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SGk=&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;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;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;This article was essentially three tutorials in one, covering methods for sending emails with Python via SMTP, a transactional email service (SendGrid), and a multichannel notifications service (Courier). With basic Python knowledge, you should now be able to choose your preference among the three solutions and easily extend your web application’s functionality when it comes to transactional emails and notifications.&lt;/p&gt;

&lt;p&gt;Author: Milan Bhardwaj&lt;/p&gt;

</description>
      <category>python</category>
      <category>ux</category>
    </item>
    <item>
      <title>Why Software Accessibility Matters</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Thu, 05 Aug 2021 17:18:15 +0000</pubDate>
      <link>https://forem.com/courier/why-software-accessibility-matters-4l60</link>
      <guid>https://forem.com/courier/why-software-accessibility-matters-4l60</guid>
      <description>&lt;p&gt;Making sure your software and its documentation is accessible is not only the right thing to do, it’s the smart thing to do—and it’s actually not that difficult. An accessible product is as usable as possible for everyone, regardless of their physical and cognitive abilities. For example, blind people should be able access your documentation with a screen reader, and neurologically atypical people shouldn’t be distracted by flashing screens, pop-ups, or carousels. &lt;/p&gt;

&lt;p&gt;All disabilities, whether permanent or temporary, can affect access to and use of your software. For many people, technology makes things easier. For people with disabilities, technology makes things possible. The general categories to account for in your design include auditory, cognitive, neurological, physical, speech, and visual disabilities. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why do it?
&lt;/h2&gt;

&lt;p&gt;Your company can have both measurable social impact and a healthy return on investment if you provide equal access to your products. Accessibility can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Drive Innovation:&lt;/strong&gt; Accessibility features in products and services can solve unanticipated problems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhance Your Brand:&lt;/strong&gt; Diversity and inclusion efforts are accelerated with a clear commitment to accessibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extend Market Reach:&lt;/strong&gt; The global market of people with disabilities is over 2 billion people with a spending power of more than $6 trillion. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minimize Legal Risk:&lt;/strong&gt; Many countries have laws and many compliance standards have criteria requiring digital accessibility.&lt;br&gt;
When accessibility is part of strategic planning, businesses are better equipped for success. A research study of Fortune 100 companies showed that disability inclusion, as part of an overall diversity strategy, is common practice among high performing businesses. &lt;br&gt;
(&lt;em&gt;&lt;a href="https://onlinelibrary.wiley.com/doi/abs/10.1002/bsl.629"&gt;Disability as diversity in fortune 100 companies&lt;/a&gt;&lt;/em&gt;. Ball, P., Monaco, G., Schmeling, J., Schartz, H., and Blanck, P.; Law, Health Policy and Disability Center (2005).)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Drive innovation
&lt;/h3&gt;

&lt;p&gt;By designing with a more diverse group of people in mind, you can develop better overall products and generate new ideas that will benefit all users. You might also uncover previously unthought of innovations that can have a measurable social impact. One example of accessible design leading to innovation for all is driverless cars. Driverless cars, which are promising for the independence of blind people, are also projected to help solve traffic fatalities and congestion.&lt;/p&gt;

&lt;p&gt;Another example comes from academia. Research and development of the artificial retina project to help restore sight for people who are blind might also help future robots with real-time image-processing systems, effectively enabling them to “see.” &lt;/p&gt;

&lt;p&gt;Accessibility is closely related to general usability in that both aim to define and deliver a more intuitive user experience. User interaction design takes into account experiences other than screens when you consider accessibility. The result is interactions that are more human-centered, natural, and contextual.&lt;/p&gt;

&lt;p&gt;Many companies use design thinking when developing products. Ideo describes design thinking as a discipline that brings together what is desirable from a human point of view with what is technologically feasible and economically viable. It also allows people who aren't trained as designers to use creative tools to address a vast range of challenges.&lt;/p&gt;

&lt;p&gt;Accessible design thinking provides varied and flexible ways for users to interact with products that can be useful for people with and without disabilities. Design thinking embraces seven core principles for participants: user-centric, collaborative, iterative, holistic, optimistic, experimental, and experiential. As you think about each of these principles, think about how accessibility can impact each one. For example, accessibility is a user-centric concept: it embraces all users, including those with disabilities. In implementing design thinking, remember to consider both the needs of your users with disabilities and the needs of your design thinking participants with disabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enhance your brand
&lt;/h3&gt;

&lt;p&gt;By developing accessible software, you demonstrate that you care about your customers as people.&lt;/p&gt;

&lt;p&gt;A genuine commitment to accessibility is an important facet of demonstrating that your company has a sense of Corporate Social Responsibility (CSR). Understanding and acting on the diverse needs of your stakeholders, both internal and external, can help create a brand that tells prospects you’re innovative, inclusive, and trustworthy. A Forrester survey commissioned by Microsoft features statements like, “Customers are aware of the needs of the people with disabilities and make purchasing decisions based on this kind of factor.” (&lt;em&gt;Assessing The Value Of Accessible Technologies For Organizations: A Total Economic Impact™ Study Commissioned By Microsoft&lt;/em&gt;. June 2016.)&lt;/p&gt;

&lt;p&gt;In addition, with a clear and integrated commitment to accessibility, you’ll enhance your internal culture and be able to hire the best people. To be successful in this endeavor, the technology that your employees use, including websites and applications, must be accessible. &lt;/p&gt;

&lt;h3&gt;
  
  
  Extend market reach
&lt;/h3&gt;

&lt;p&gt;The market of people with disabilities is large, and it’s growing as the population ages. In the US alone, the annual discretionary spending of people with disabilities is over $200 billion. &lt;/p&gt;

&lt;p&gt;Consider the following facts when estimating market size. At least one billion people —15% of the world’s population—have a recognized disability. As the global population ages, many more people acquire a disability, which means that in countries with life expectancies of over 70 years of age, people spend 11.5% of their lifespan living with a disability. Globally, this market is estimated at 2.3 billion people who control $6.9 trillion in annual disposable income.&lt;/p&gt;

&lt;p&gt;Think of it this way: Not having an accessible product is the modern-day equivalent of kicking out every fifth customer that enters your business.&lt;/p&gt;

&lt;p&gt;Accessible product design often leads to improvements in general customer experience and thus customer loyalty. For customers with disabilities, such improvements are essential for equal access. Moreover, accessible design provides options that are useful to all customers in various situations. For example, accessible software benefits not only users with disabilities, but also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Older people with changing abilities due to aging&lt;/li&gt;
&lt;li&gt;People with “temporary disabilities” such as carpal tunnel syndrome, a broken arm, or lost glasses&lt;/li&gt;
&lt;li&gt;Users with challenging situations like bright, glaring sunlight or noisy environments&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Minimize legal risk
&lt;/h3&gt;

&lt;p&gt;In many industries, accessibility is required for compliance. Even if your business is not legally required to be accessible, you might find that your customers are only permitted to work with companies that are compliant. Factoring in regulation, government oversight, and increased court action, the legal landscape is rapidly changing in favor of equal access.&lt;/p&gt;

&lt;p&gt;Accessibility is a human right. The United Nations Convention on the Rights of People with Disabilities (CRPD) is a comprehensive human rights document that includes a direct reference to the rights of all people to have equal access to communications technology, which includes software as well as the web. Passed by the General Assembly of the UN, at last count more than 182 countries have ratified the CRPD. (As of 2021, the United States has signed, but not ratified, the treaty.)&lt;/p&gt;

&lt;p&gt;The European Commission has adopted the European Accessibility Act, requiring ATMs and banking services, PCs, telephones and TV equipment, telephony and audiovisual services, transport, e-books, and e-commerce meet accessibility requirements. Australia, Canada, and other European countries have similar laws. Software is often included in these laws.&lt;/p&gt;

&lt;p&gt;In the US, the number of lawsuits continues to rise, and courts increasingly decide in favor of equal access, often citing the Americans with Disabilities Act (ADA). If your product is used by the government, you also need to conform to Section 508, which covers a range of technology including electronic documents, software applications, web content, operating systems, and development frameworks. &lt;/p&gt;

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

&lt;p&gt;Making software accessible is the right thing to do, and if you begin with accessibility in mind, it’s easier than going back and building it in later. It’s also good engineering practice to keep accessibility in mind from the start. Here at Courier, we are eager to dive into the project of making our own product more accessible. Admittedly, we’re not quite there yet, but we’re ready to get started! A few simple, but effective steps we plan to keep top-of-mind are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Familiarize ourselves with the &lt;a href="https://webaim.org/techniques/aria/"&gt;ARIA standards&lt;/a&gt;. The Accessible Rich Internet Applications Suite defines a way to make Web content and Web applications more accessible to people with disabilities.&lt;/li&gt;
&lt;li&gt;Offer &lt;a href="https://webaim.org/techniques/captions/realtime"&gt;real-time captioning&lt;/a&gt; for any of our live online content.&lt;/li&gt;
&lt;li&gt;Ensure our colors meet &lt;a href="http://webaim.org/resources/contrastchecker/"&gt;minimum contrast requirements&lt;/a&gt; everywhere, including this blog.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more on accessibility such as how to engineer for accessibility and how you can use notifications to help your accessibility program, watch this space!&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>cloud</category>
      <category>a11y</category>
    </item>
    <item>
      <title>How to Send Emails with Node.js</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Mon, 26 Jul 2021 19:23:27 +0000</pubDate>
      <link>https://forem.com/courier/how-to-send-emails-with-node-js-49jc</link>
      <guid>https://forem.com/courier/how-to-send-emails-with-node-js-49jc</guid>
      <description>&lt;p&gt;Almost every web application needs the functionality to send transactional emails in response to various triggers. Events like account registration, password resets, purchase receipts, and user verification are among the many tasks today’s applications need to accomplish via email. These emails are crucial for notifying users of important updates and enabling key user workflows in your application.&lt;/p&gt;

&lt;p&gt;This post explores three different options for sending email from within a Node.js app. I’ll share each method’s pros and cons, so you can select the best method for your needs.&lt;/p&gt;

&lt;h1&gt;
  
  
  3 options for sending email with Node.js
&lt;/h1&gt;

&lt;p&gt;As a server-side tool, Node.js allows you to send emails using a few different options. I’ll provide an overview of the three main options — SMTP, email API, and multi-channel notification service — before diving into a technical tutorial for each of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Using SMTP
&lt;/h2&gt;

&lt;p&gt;Simple Mail Transfer Protocol (SMTP) is a technology for sending outgoing emails across networks and is the most common transport method. It serves as a relay service to send email from one server to another.&lt;/p&gt;

&lt;p&gt;When you send an email to a friend using an email client like Gmail, an outgoing (SMTP) server picks it up and connects with your friend’s receiving server. The two servers communicate using guidelines defined by the SMTP protocol, determining who the recipient is and how they can receive the incoming mail. Email clients usually have an SMTP server associated with them to aid in email delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of using SMTP
&lt;/h3&gt;

&lt;p&gt;The major advantage of SMTP is that it’s widely adopted and easy to set up and integrate in a web application. Email service providers, which I cover below, might have more features, but using them also means relying on a third-party intermediary to deliver your emails. With SMTP, you get fine-grained control over every aspect of your email sending.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawbacks of using SMTP
&lt;/h3&gt;

&lt;p&gt;The major drawback of SMTP is &lt;a href="https://blog.mailtrap.io/smtp-security/"&gt;it can be insecure and easily hacked&lt;/a&gt;. The standard SMTP protocol is susceptible to DDoS attacks, phishing, and data breaches. If you decide to use your own email SMTP server, you will be responsible for long-term server maintenance, which requires a lot of ongoing effort to maintain securely.&lt;/p&gt;

&lt;p&gt;Sending emails with SMTP is also much slower than using an API service. SMTP requires extensive back-and-forth between mail SMTP servers to deliver a message. Even then the email may fail to deliver without feedback if the server’s IP address is blacklisted or a firewall has blocked a port. This back-and-forth also means multiple points of failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Using a transactional email API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.courier.com/blog/best-transactional-email-api-service"&gt;Transactional email services&lt;/a&gt; allow you to send email from your app using a hosted API. Instead of managing email servers and their requirements yourself, you can use an email API to handle message assembly, sending, and deliverability. Transactional email APIs come in handy when you need a reliable service that can be integrated quickly, can support high-volume sending, and offers rich functionality.&lt;/p&gt;

&lt;p&gt;There are many transactional email services on the market. The most popular ones include &lt;a href="https://docs.courier.com/docs/aws-ses"&gt;Amazon SES&lt;/a&gt;, &lt;a href="https://docs.courier.com/docs/postmark"&gt;Postmark&lt;/a&gt;, &lt;a href="https://docs.courier.com/docs/sparkpost"&gt;SparkPost&lt;/a&gt;, &lt;a href="https://docs.courier.com/docs/getting-started-sendgrid"&gt;SendGrid&lt;/a&gt;, &lt;a href="https://docs.courier.com/docs/mailgun"&gt;Mailgun&lt;/a&gt;, and &lt;a href="https://docs.courier.com/docs/mandrill"&gt;Mailchimp Transactional&lt;/a&gt; (formerly Mandrill). All of them are paid services, though most offer free or low-cost introductory plans.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of using a transactional email API
&lt;/h3&gt;

&lt;p&gt;The main advantage of using a transactional email service is they’re very easy to set up and easy to use, especially since most services come with comprehensive documentation. They provide monitoring, such as whether emails are delivering, as well as web analytics and reporting, including bounce rate, open, click, and unsubscribe tracking.&lt;/p&gt;

&lt;p&gt;Other key advantages of using an email API are that they’re highly scalable, they add an extra layer of security by utilizing API keys as opposed to the SMTP method, and they can save you significant engineering time and costs when it comes to ongoing maintenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawbacks of using a transactional email API
&lt;/h3&gt;

&lt;p&gt;The major drawback of using a hosted email service, instead of SMTP, is you’re relying on a third-party to handle your emails. Before picking a provider, spend some time &lt;a href="https://www.courier.com/blog/best-transactional-email-api-service"&gt;researching&lt;/a&gt; their features, guaranteed uptime, email deliverability rates, and API documentation.&lt;/p&gt;

&lt;p&gt;The other drawback of using a hosted email service, instead of a &lt;a href="https://www.courier.com/"&gt;multi-channel notifications service&lt;/a&gt; (which I cover below), is if your application needed to notify users on other channels, you’d have to integrate each new channel separately. For example, you’d have to separately integrate mobile and web push, SMS, and chat apps like Slack and WhatsApp. Whether all the extra code and effort is worth it is up to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Using a multi-channel notification service
&lt;/h2&gt;

&lt;p&gt;Multichannel notification services, such as &lt;a href="https://www.courier.com/"&gt;Courier&lt;/a&gt;, allow you to reach users across a number of different channels using one uniform API. They usually allow you to bring your own provider for each channel; in the case of email, that could be your own SMTP server or a hosted transactional email API.&lt;/p&gt;

&lt;p&gt;With a multichannel notification service, you can easily add more channels or even switch your email service provider without having to touch your code. If you wanted to notify users across email, SMS, push, or chat apps like Slack and WhatsApp, you could do that in one fell swoop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.courier.com/"&gt;Courier&lt;/a&gt;, in particular, gives you additional functionality — on top of what you’d get with a transactional email service. You can design your emails in a flexible visual and code editor, set delivery rules and create simple workflows, and monitor delivery status in real-time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of using a multichannel notification service
&lt;/h3&gt;

&lt;p&gt;The major advantage of using a multichannel notification service is you can easily start sending notifications from other channels, such as SMS, push, and chat apps, using the same API. This means there’s less code to maintain when integrating multiple services and no additional work required to add a new channel or switch providers.&lt;/p&gt;

&lt;p&gt;Another advantage of using a service like &lt;a href="https://www.courier.com/"&gt;Courier&lt;/a&gt; is it allows non-technical users to edit the content, styling, and even branding of outgoing emails without involving developers or deploying code. You can easily preview your emails in Courier using dummy data and safely troubleshoot notifications in a separate test environment before pushing to production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawbacks of using a multichannel notification service
&lt;/h3&gt;

&lt;p&gt;The drawbacks of using a multichannel notification service are similar to directly integrating with a transactional email API. You are relying on a third party to manage your message assembly, sending, and delivery. Plan to spend time researching your options and exploring the product before making a decision. Courier has a &lt;a href="https://app.courier.com/register"&gt;generous free plan&lt;/a&gt;, which includes 10,000 notifications per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: How to send emails with Nodemailer and SMTP
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nodemailer.com/about/"&gt;Nodemailer&lt;/a&gt; is a Node.js module used for sending emails and is the most popular Node.js email package. You can use Nodemailer to create HTML or plain-text emails, add attachments, and send your emails through different transport methods, including built-in SMTP support. It requires Node.js 6.0 or newer.&lt;/p&gt;

&lt;p&gt;Let’s walk through how to send email using Nodemailer. The first step is to create a Node.js application:&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;mkdir&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;nodeapp&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;cd&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;nodeapp&lt;/span&gt; 
    &lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you’ve created a folder and initialized a &lt;code&gt;package.json&lt;/code&gt; file using the &lt;code&gt;npm init&lt;/code&gt; command. The &lt;code&gt;-y&lt;/code&gt; flag is there to skip the interactive back-and-forth questions by npm.&lt;/p&gt;

&lt;p&gt;Next, install the Nodemailer module:&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;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nodemailer’s &lt;code&gt;createTransport&lt;/code&gt; function specifies which method you want to use for sending email. It takes the connection data and credentials as an argument. In this case, since SMTP is the preferred transport, you will need to define an SMTP host, port, and credential password for accessing a host SMTP server.&lt;/p&gt;

&lt;p&gt;To get a host URL, you need an SMTP server. For development purposes, you can use &lt;a href="https://mailtrap.io/"&gt;Mailtrap&lt;/a&gt;, or a similar service, to serve as a fake SMTP server. A fake SMTP server lets you avoid cluttering your real account with multiple tests while still seeing how your test emails behave — do all the buttons work the way they’re supposed to, is the formatting still correct after sending, and so on.&lt;/p&gt;

&lt;p&gt;Create a Mailtrap account if you don’t already have one. In the Integrations dropdown on the dashboard, select &lt;em&gt;Nodemailer&lt;/em&gt; and copy the credentials displayed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--__HkXz66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AcwU7JUCvDwVKYq-o" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--__HkXz66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AcwU7JUCvDwVKYq-o" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create an &lt;code&gt;email.js&lt;/code&gt; file and add the following:&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;nodemailer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodemailer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;transporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
             &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;smtp.mailtrap.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2525&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="na"&gt;user&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;user&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;pass&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;pass&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;Substitute the host, user, and password with the Mailtrap credentials you copied from the dashboard above. Now you can send an email using the &lt;code&gt;sendMail&lt;/code&gt; method of Nodemailer’s &lt;code&gt;createTransport&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Append the following to the &lt;code&gt;email.js&lt;/code&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="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from-example@email.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;to-example@email.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello SMTP Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&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;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="k"&gt;if&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="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="k"&gt;else&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="nx"&gt;info&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;Nodemailer also supports sending emails using HTML. All you need to do is add the &lt;code&gt;html&lt;/code&gt; attribute to your message object like so:&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from@email.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;to@email.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="na"&gt;html&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 SMTP Email&amp;lt;/h1&amp;gt;&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;To test that it works, go to your terminal and run:&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;node&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;js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to your Mailtrap dashboard to see your email was received.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fFL7UOP9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AZy2xNjd6kx_rZ5Z2" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFL7UOP9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AZy2xNjd6kx_rZ5Z2" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: How to send emails using a transactional email API
&lt;/h2&gt;

&lt;p&gt;There are a variety of email-as-a-service platforms and APIs, such as &lt;a href="https://sendgrid.com/"&gt;SendGrid&lt;/a&gt; and &lt;a href="https://www.mailgun.com/"&gt;Mailgun&lt;/a&gt;, among others. For this article, I’ll demonstrate sending emails from within a Node application using SendGrid, which allows you to send up to 100 emails per month for free.&lt;/p&gt;

&lt;p&gt;To start sending emails with SendGrid, the first step is to &lt;a href="https://signup.sendgrid.com/"&gt;sign up&lt;/a&gt; for the service. Then you’ll need to create a SendGrid API key for sending email.&lt;/p&gt;

&lt;p&gt;To create an API key, go to Settings &amp;gt; API Keys on SendGrid’s dashboard, then click “Create API Key.” Give the key a name, select “Full Access,” then click “Create &amp;amp; View.” Copy your API key and keep it safe for later use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6_GtNjz6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AeJK4HaRhyhjLfswM" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6_GtNjz6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AeJK4HaRhyhjLfswM" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, install the SendGrid JavaScript client with npm:&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;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;sendgrid&lt;/span&gt;&lt;span class="sr"&gt;/mai&lt;/span&gt;&lt;span class="err"&gt;l
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file in your project directory named &lt;code&gt;sendgrid.js&lt;/code&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="nx"&gt;touch&lt;/span&gt; &lt;span class="nx"&gt;sendgrid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;sendgrid.js&lt;/code&gt; file, add the following lines of code:&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;sendgrid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sendgrid/mail&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;SENDGRID_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;SENDGRID_API_KEY&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

    &lt;span class="nx"&gt;sendgrid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SENDGRID_API_KEY&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;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="c1"&gt;// Change to your recipient&lt;/span&gt;
       &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="c1"&gt;// Change to your verified sender&lt;/span&gt;
       &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sending with SendGrid Is Fun&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;and easy to do anywhere, even with Node.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;strong&amp;gt;and easy to do anywhere, even with Node.js&amp;lt;/strong&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;span class="nx"&gt;sendgrid&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="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resp&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="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;Email sent&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resp&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;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;Replace the variable &lt;code&gt;SENDGRID_API_KEY&lt;/code&gt; with the SendGrid API key you created previously and make sure the email address in the From field has been verified by SendGrid. You can do this by creating a &lt;a href="https://sendgrid.com/docs/ui/sending-email/sender-verification"&gt;sender identity&lt;/a&gt;. This verifies that the email address actually belongs to you. Also, replace the email address in the To field from &lt;code&gt;test@example.com&lt;/code&gt; to your test recipient.&lt;/p&gt;

&lt;p&gt;To test that it works, run:&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;node&lt;/span&gt; &lt;span class="nx"&gt;sendgrid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see if your email was delivered, check the SendGrid dashboard, and on the sidebar, select “Activity.” There, you should see the email you just sent. SendGrid will show you whether it was delivered or not and whether it has been opened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: How to send emails using a multichannel notification service
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.courier.com/"&gt;Courier&lt;/a&gt; is a multichannel notifications platform that enables you to reach your users on any channel using one uniform API. With Courier, you can bring &lt;a href="https://docs.courier.com/docs/email-integrations"&gt;your own email service provider&lt;/a&gt;, including &lt;a href="https://docs.courier.com/docs/smtp"&gt;SMTP&lt;/a&gt; or Gmail, or any of the popular email APIs like &lt;a href="https://docs.courier.com/docs/sendgrid"&gt;SendGrid&lt;/a&gt;, &lt;a href="https://docs.courier.com/docs/aws-ses"&gt;Amazon SES&lt;/a&gt;, and &lt;a href="https://docs.courier.com/docs/postmark"&gt;Postmark&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To start using Courier, &lt;a href="https://app.courier.com/register"&gt;create an account&lt;/a&gt;. You can send up to 10,000 notifications per month for free. During the onboarding flow, you’ll be asked to give Courier permission to send email on your behalf from your Gmail account. You can skip this step if you’re planning on using a different ESP, but we recommend setting it up as the fastest way to test out sending from Courier.&lt;/p&gt;

&lt;p&gt;To use Courier to send transactional emails, head to the Courier dashboard and select &lt;a href="https://app.courier.com/designer/notifications"&gt;Designer&lt;/a&gt; on the lefthand menu. Then, click the “Create Notification” button.&lt;/p&gt;

&lt;p&gt;Select Gmail in the provider selection modal and hit “Continue”. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hfYmiOO_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sm212nhdxh3f7wl4gb1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hfYmiOO_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sm212nhdxh3f7wl4gb1l.png" alt="Choosing gmail provider in Courier dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there, you’ll want to add the content for your email notification. You can use the toolbar to drag and drop blocks for text, images, buttons, and more. You can even add Markdown or add code blocks to further customize your email.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XUz1-W7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AQDvi2irE2fARTwop" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XUz1-W7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AQDvi2irE2fARTwop" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, send the email notification from within Node.js using the Courier npm package&lt;a href="https://github.com/trycourier/courier-node"&gt;&lt;code&gt;@trycourier/courier&lt;/code&gt;&lt;/a&gt;. To install it, run:&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;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;trycourier&lt;/span&gt;&lt;span class="sr"&gt;/courie&lt;/span&gt;&lt;span class="err"&gt;r
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file in your app directory named &lt;code&gt;courier.js&lt;/code&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="nx"&gt;touch&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;js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Courier will automatically generate a code snippet for your notification, which you can copy-paste from the Send tab. Add the following lines of code to the file:&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="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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&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="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="o"&gt;*&lt;/span&gt;&lt;span class="c1"&gt;// your Notification ID&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&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&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;&amp;lt;EMAIL_ADDRESS&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;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="c1"&gt;// optional variables for merging into templates }).then((resp) =&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;Email sent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resp&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;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;The Courier package is imported into the file, and the Courier client is instantiated. The client takes an authentication token, which you can get from the Courier notification settings created earlier. Click the gear icon from within your notification and copy the masked auth token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Tv2HUaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AHAei4D1-cOF3Gjeq" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Tv2HUaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/0%2AHAei4D1-cOF3Gjeq" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Courier client has a send method which takes an event ID, which is either the &lt;a href="https://help.courier.com/en/articles/4333977-notification-settings-overview"&gt;notification ID&lt;/a&gt; or &lt;a href="https://help.courier.com/en/articles/4202416-how-to-create-and-map-event-triggers-for-your-notifications"&gt;custom event that you’ve mapped to your notification&lt;/a&gt;. The recipient Id should be a unique string you can use to identify the recipient and look them up in data logs. Note that &lt;code&gt;email&lt;/code&gt; refers to the email address of the recipient.&lt;/p&gt;

&lt;p&gt;To check the status of your email, head to the &lt;a href="https://app.courier.com/data/messages"&gt;Data tab&lt;/a&gt; in your Courier dashboard. Courier will tell you if your email has been delivered, opened, and/or clicked. Courier will also tell you if there are any errors and when in the delivery pipeline they occurred.&lt;/p&gt;

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

&lt;p&gt;In this guide, we’ve explored methods for sending email in a Node.js web application. You’ve learned how to use SMTP and Nodemailer, a transactional email service (in this case, SendGrid), and a multichannel notifications service (in this case, &lt;a href="https://www.courier.com/"&gt;Courier&lt;/a&gt;). Hopefully, reviewing these pros and cons will help you pick the best option for efficiently and securely sending emails in your web application.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>node</category>
      <category>architecture</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Notifications Strategy that Put Us in the Product Hunt Newsletter</title>
      <dc:creator>Micah Zayner</dc:creator>
      <pubDate>Mon, 19 Jul 2021 18:15:01 +0000</pubDate>
      <link>https://forem.com/courier/the-notifications-strategy-that-put-us-in-the-product-hunt-newsletter-3h3l</link>
      <guid>https://forem.com/courier/the-notifications-strategy-that-put-us-in-the-product-hunt-newsletter-3h3l</guid>
      <description>&lt;p&gt;Getting noticed on Product Hunt is a start-up founder's dream, but it’s not an easy task. There’s only one chance at listing a specific domain, so it can’t be a short sighted attempt. The internet is a web of communication, and depending on how it is used, teams can effectively notify their communities about their &lt;a href="https://www.producthunt.com/"&gt;Product Hunt&lt;/a&gt; launch. We realized that the best route forward would be using a sophisticated notification strategy to increase timely engagement with the brand via the Product Hunt page. We were able to execute that strategy which helped us get noticed as we ranked #5 on our launch day and were the featured story in the Product Hunt newsletter the next day. Here is how we did it.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/63lOA8lsPpazFN5UbZLElA/1bebc0e0032db35c11d70eb67e8686cb/image5.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/63lOA8lsPpazFN5UbZLElA/1bebc0e0032db35c11d70eb67e8686cb/image5.png" alt="Launch day card"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Notifications that Don't Suck: Making it into the Newsletter
&lt;/h3&gt;

&lt;p&gt;If you are trying to make it into the newsletter, understand that Product Hunt is looking for a story, and they have no issues with an edgy one. In our case, we referenced WUPHF.com, a fake social network from the TV show &lt;em&gt;The Office&lt;/em&gt; (US). It was our CEO Troy’s idea to take that route. We had bumped into jokes about our likeness to the idea in the past but thought it was a great opportunity to embrace it. It was one of &lt;em&gt;The Office's&lt;/em&gt; funniest bits, but was also surprisingly relevant. &lt;/p&gt;

&lt;p&gt;As users, we don't want to miss important notifications. The concept WUPHF triggered alerts to your cell phone, home phone, email, Facebook, Twitter, and fax. Being plugged into every provider is the correct next step, but where they got it jokingly wrong was sending it to every provider at the same time. We wanted to highlight how our platform helps ensure notifications are sent through the right channel at the right time, minimizing distracting and annoying notifications and maximizing user delight. Here is the relevant &lt;a href="https://youtu.be/15whn40EQww"&gt;clip&lt;/a&gt; from one of the episodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building an Engaged Audience
&lt;/h3&gt;

&lt;p&gt;Our first step was to identify where our community is concentrated and who they are. This includes social networks, chat servers, and email lists. Our goal here is to not burn the farm, but pick the appropriate connections to make the most of our databases and networks. There is a life after the launch to think about. We want to identify an audience that is engaged, likely to give feedback, and possibly is already a product hunt member. &lt;/p&gt;

&lt;p&gt;To start, we went into our Hubspot CRM, cleaned out a lot of dead data, and made a focused email list of people we thought would be most likely interested in this event. We did the same for our Intercom user base, recognizing we only wanted to interact with some users. This is a crucial step for launch day if you want to minimize unsubscribes or negative reactions as well as get the most impact from your audience. We planned an email notification scheduled from a single time zone from Hubspot and we hosted a banner on our marketing site from Intercom that said, “We're launching on Product Hunt today. If you're a product hunter, we'd love your feedback 💜 Share your feedback.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Social Media Engagement
&lt;/h3&gt;

&lt;p&gt;After we had our internal communications strategy, we moved on to our social platforms. We used Hubspot’s social media feature to schedule posts leading up to the event. We made sure to tag Product Hunt in every post. Understanding that our company page posting wasn’t going to be enough, we built out a Google Doc with full instructions for our employees on how they could participate in the days leading up to and on launch day. An effective tool to send notifications to your employees is Google Calendar. We published an event on the calendar that they could voluntarily (we believe in opt-in/opt-out communication) subscribe to notifying them of each day’s activities. A powerful tool that allows employees to easily distribute tweets via Twitter is Clicktotweet.com. Here, you can have a pipeline of tweets embedded in easy-to-click links ready and loaded just waiting for someone to tweet from their profile. &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/39im1nNoPSmkIqs8NWQk0P/f5dada7e2454e16c94530e677658c154/image4.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/39im1nNoPSmkIqs8NWQk0P/f5dada7e2454e16c94530e677658c154/image4.png" alt="Example Tweet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a great way to mass distribute your message. Notice the way the link preview looks as well.&lt;/p&gt;

&lt;p&gt;First image on your hunt page is displayed. &lt;br&gt;
[Name of the product] - [tagline]&lt;br&gt;
[description]&lt;/p&gt;

&lt;p&gt;The link is how most people will be introduced to your hunt page, so it’s important to keep formatting in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syncing the LinkedIn Event with LinkedIn Live
&lt;/h3&gt;

&lt;p&gt;This is where we used our love for and understanding of LinkedIn’s Air Traffic Controller. We made a LinkedIn Event synced to a LinkedIn Live broadcast. This is how we were able to funnel all of our first degree LinkedIn connections as a company. Many people have a LinkedIn account, at least in the US, and this helps to easily invite people to an event. Product Hunt does offer a service called Ship that collects emails and allows you to send messages, but we wanted the path that requested the least information possible. LinkedIn event invitations send a notification to the &lt;em&gt;My Followers&lt;/em&gt; section in the app. &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/4rmRyvOYZVshcYFmsyz47L/7cae71a12eafa046962d4f7d1c306910/image7.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/4rmRyvOYZVshcYFmsyz47L/7cae71a12eafa046962d4f7d1c306910/image7.png" alt="image7"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a user accepts the invite, LinkedIn then depends on the their notification preferences to send them a series of emails, in-app, or push notifications reminding them of the event: one week before, the Monday before, and the day of. &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/2LBsq9ucN2tiUOvXliIsGn/f9952ed3c1f8b28f4e63c23de641bf6b/image6.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/2LBsq9ucN2tiUOvXliIsGn/f9952ed3c1f8b28f4e63c23de641bf6b/image6.png" alt="LinkedIn Reminder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the event admin, you have the option to maintain an event chat that attendees can join like a group conversation. You can also start by posting a poll to get engagement rolling leading up to the event.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/2qGdOcxlfgPf8p1f7wqu4e/40a9a8d4758179774f04c8bd423b5040/image3.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/2qGdOcxlfgPf8p1f7wqu4e/40a9a8d4758179774f04c8bd423b5040/image3.png" alt="Event Actions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you post in this event, you have the option to notify all attendees. We had an initial post that greeted attendees and introduced them to Courier if they were unfamiliar and we posted a video the day of our launch as an introduction to let our community know that the hunt page was live and they were welcome to interact as they pleased, and this is where we used the "notify attendees" button. &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/3WTEHBXotRSD6K2yV6RhZ/5a36afa4daea3ae0e6117e72fbd71501/image1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/3WTEHBXotRSD6K2yV6RhZ/5a36afa4daea3ae0e6117e72fbd71501/image1.png" alt="Notifying attendees"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Half way through launch day, we finally went &lt;a href="https://www.linkedin.com/posts/activity-6813934971049267200-fdog"&gt;live&lt;/a&gt; on our LinkedIn Event and talked about the experience, thanking everyone for their participation.&lt;/p&gt;

&lt;p&gt;Outside of the LinkedIn event, we also had a banner placed on our marketing website on the day of the launch using Intercom and we posted in our &lt;a href="https://discord.gg/courier"&gt;Discord community&lt;/a&gt;. Members of our team, including the CEO, also reached out to their extended Slack groups and Discord servers with a soft invite to check out the hunt page.&lt;/p&gt;

&lt;p&gt;Fortunately we have a lot of support from our funding network. We were able to have a representative from our accelerator “hunt” the page for us. Our accelerator relationship has also forged a network of users and soon-to-be users that enjoy being a part of the fun of the day, sometimes re-tweeting posts and sharing to support us.&lt;/p&gt;

&lt;p&gt;By the end of launch day, we ranked in the Top 5 with around 280 upvotes and collected around 70 positive comments and 10 reviews from the community. I'm not going to lie, I kind of went to bed feeling a little defeated. When I woke up the next morning, I did my usual routine and sat down to write a fairly somber and introspective post mortem, when all of a sudden at 7:52 am, I received the newsletter. &lt;em&gt;Notifications that don’t suck&lt;/em&gt; was the subject line! I thought it was a joke. I opened it up, and there we were, the featured story. The rest of the day was full of a bunch more traffic, another 150+ upvotes, 20 more comments, and one of our best sign-up days ever.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.contentful.com/z7iqk1q8njt4/2qzaxqBBRYpkZH8aWrirqP/d1aeefb0500b81a05a7f5e8888216891/image2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/z7iqk1q8njt4/2qzaxqBBRYpkZH8aWrirqP/d1aeefb0500b81a05a7f5e8888216891/image2.jpg" alt="Product Hunt Newsletter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our notifications strategy involved sending the right communication at the right time, which is ultimately what generated traffic to our Product Hunt page. Considering we have an existing customer and user base, we knew that a lot of our fans love us, but we also found an entire community out there that had yet to hear about us that was also interested in trying out our Courier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our Recommendations For You
&lt;/h3&gt;

&lt;p&gt;For reference, this is the &lt;a href="https://gleam.io/blog/product-hunt/"&gt;post&lt;/a&gt; I followed to learn what I should focus on throughout the launch. Most importantly, make sure you follow the Product Hunt &lt;a href="https://www.producthunt.com/protips"&gt;Guidelines&lt;/a&gt; for posting! Below are some key recommendations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schedule out your post in advance. This allows you to see it live and editable, but excludes interactions so that you can see how everything looks.&lt;/li&gt;
&lt;li&gt;Subscribe to the newsletter and see which posts are getting featured. Become a member of the Product Hunt community early to get an understanding of how it all works. In regards to your hunt page: be edgy, have a theme, don’t just pitch a product, and have fun with it.&lt;/li&gt;
&lt;li&gt;Follow &lt;a href="https://twitter.com/5harath"&gt;@5harath&lt;/a&gt;, &lt;a href="https://twitter.com/jakecrump"&gt;@jakecrump&lt;/a&gt;, &lt;a href="https://twitter.com/AdityaVSC"&gt;@adityavsc&lt;/a&gt;, &lt;a href="https://twitter.com/Leandro8209"&gt;@leandro8209&lt;/a&gt;, &lt;a href="https://twitter.com/calum"&gt;@calum&lt;/a&gt; (their community managers) and &lt;a href="https://twitter.com/ProductHunt"&gt;@producthunt&lt;/a&gt; on Twitter &lt;/li&gt;
&lt;li&gt;Be careful not to abuse your access to notifying individuals on any platform, or people will unsubscribe and lose interest in your company.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to learn more about LinkedIn’s notification architecture, join us on Tuesday August 3rd for a [LinkedIn Live special event]&lt;a href="https://www.linkedin.com/events/6818949675652026370/"&gt;https://www.linkedin.com/events/6818949675652026370/&lt;/a&gt;) where our CEO talks to Sandor Nyako, Director of Engineering, from the Air Traffic Controller team.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>startup</category>
      <category>notifications</category>
    </item>
  </channel>
</rss>
