<?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>DEV Community: Sailhouse</title>
    <description>The latest articles on DEV Community by Sailhouse (@sailhouse).</description>
    <link>https://dev.to/sailhouse</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%2Forganization%2Fprofile_image%2F10597%2F119e6808-6987-4679-aff1-f629d9f2a2f8.png</url>
      <title>DEV Community: Sailhouse</title>
      <link>https://dev.to/sailhouse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sailhouse"/>
    <language>en</language>
    <item>
      <title>Building an Email Confirmation Flow with Sailhouse, Netlify, and Resend</title>
      <dc:creator>Dr Claire Knight</dc:creator>
      <pubDate>Fri, 28 Mar 2025 21:15:55 +0000</pubDate>
      <link>https://dev.to/sailhouse/building-an-email-confirmation-flow-with-sailhouse-netlify-and-resend-14b8</link>
      <guid>https://dev.to/sailhouse/building-an-email-confirmation-flow-with-sailhouse-netlify-and-resend-14b8</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: I built an open-source email confirmation system using Sailhouse's event architecture and Netlify serverless functions, using Resend for the emails. Grab it, hack it, use it! &lt;a href="https://github.com/sailhouse/email-confirmation" rel="noopener noreferrer"&gt;github.com/sailhouse/email-confirmation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Introduction: Why Email Confirmation Matters
&lt;/h2&gt;

&lt;p&gt;It's one of those things every project needs, but nobody's particularly excited to implement. These flows do important stuff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They stop the bots (mostly)&lt;/li&gt;
&lt;li&gt;Keep you on the right side of privacy laws&lt;/li&gt;
&lt;li&gt;Show users you're not going to spam them into oblivion&lt;/li&gt;
&lt;li&gt;Help maintain a list of people who actually want your emails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After 20 years of working with these things (and seeing them break in production 😅), I wanted something simple that just works without becoming another headache.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Common Email Confirmation Considerations
&lt;/h2&gt;

&lt;p&gt;Let's be real about what we all think when approaching this problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Do I really need to build this again?"&lt;/strong&gt; - The eternal build vs. buy dilemma&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"This shouldn't be so complicated"&lt;/strong&gt; - Yet somehow, it always is&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"I don't want to pay $X per month just for confirmations"&lt;/strong&gt; - Especially for side projects&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;I don’t need a full on CRM/growth hacking platform&lt;/strong&gt;” - A lighter solution for informational messages can be enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"But I need it to work with my weird edge case"&lt;/strong&gt; - Because requirements are never standard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"I want control without the maintenance nightmare"&lt;/strong&gt; - The developer's dream&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I finally decided to build something reusable. Classic developer move, right? "I'll save time in the long run!" 🙃 But I did enjoy myself doing it. So there’s that.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Power of Event-Driven Architecture with Sailhouse
&lt;/h2&gt;

&lt;p&gt;Event-driven architecture is one of those approaches that just makes sense once you try it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Components talk through events, not direct calls - way less "you changed your API and broke my stuff" moments&lt;/li&gt;
&lt;li&gt;If one part breaks, the whole system doesn't necessarily collapse&lt;/li&gt;
&lt;li&gt;Handles traffic spikes gracefully - events just queue up&lt;/li&gt;
&lt;li&gt;It can even makes the system easier to reason about - "this happens, then that happens"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built Sailhouse to make this kind of architecture accessible without needing a PhD in distributed systems. It's the infrastructure we both wish we'd had for the last decade of projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Solution Architecture Overview 🏗️
&lt;/h2&gt;

&lt;p&gt;Here's what's going on under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sailhouse&lt;/strong&gt;: Handles all the event passing and sequencing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netlify Functions&lt;/strong&gt;: Lightweight serverless bits that process the events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events for state&lt;/strong&gt;: No database needed - the event stream itself maintains state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pluggable email delivery&lt;/strong&gt;: Works with whatever you're using to send emails. In production we are using Resend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since we are using this to confirm subscription via email to our &lt;a href="http://sailhouse.dev/changelog" rel="noopener noreferrer"&gt;changlog&lt;/a&gt; The flow is pretty straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User submits their email&lt;/li&gt;
&lt;li&gt;We fire off a &lt;code&gt;changelog-subscription&lt;/code&gt; event (steps 1 and 2 are from our website)&lt;/li&gt;
&lt;li&gt;Sailhouse processes the event, and there is a subscription setup in Sailhouse to ping a Netlify Function to process the event and create a secure token&lt;/li&gt;
&lt;li&gt;Email with confirmation link gets sent&lt;/li&gt;
&lt;li&gt;User clicks the link (hopefully!)&lt;/li&gt;
&lt;li&gt;System verifies the token and publishes &lt;code&gt;changelog-confirmation&lt;/code&gt;, and saves the email address&lt;/li&gt;
&lt;li&gt;The confirmed email can then be handled either via another function, or subscribing to events or, indeed be used to update Slack.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No SQL queries, no complex state management. Events all the way down!&lt;/p&gt;

&lt;p&gt;Oh, I should mention - we're using Netlify Blob Store to persist subscriber data. It's a nice little feature that Netlify provides out of the box, so we don't need to set up a separate database. The events drive the system, and the blob store gives us somewhere to stash the data for later retrieval or processing. It's surprisingly powerful for something that requires zero setup!&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Implementation Highlights
&lt;/h2&gt;

&lt;p&gt;I've focused on making this both solid and developer-friendly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything communicates through well-defined events&lt;/li&gt;
&lt;li&gt;Tokens are properly secured (because security matters)&lt;/li&gt;
&lt;li&gt;Email templates you can actually customise without fighting the system; thank you &lt;a href="https://react.email/" rel="noopener noreferrer"&gt;react-email&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sensible defaults but configurable where it counts&lt;/li&gt;
&lt;li&gt;Error handling that won't leave you debugging at 2am&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system starts with the &lt;code&gt;subscription-handler.ts&lt;/code&gt; function which receives events from Sailhouse and validates they were truly from Sailhouse&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shSignature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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;sh-signature&lt;/span&gt;&lt;span class="dl"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;shSignature&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;shSignature&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;sailhouseSignature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&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;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&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 service uses React Email to create responsive, customisable email templates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In ConfirmationEmail.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ConfirmationEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ConfirmationEmailProps&lt;/span&gt;&lt;span class="o"&gt;&amp;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;confirmationUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Generated for you with an encoded token&lt;/span&gt;
  &lt;span class="nx"&gt;mailingListName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Name of the list - we used Changlog here&lt;/span&gt;
  &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// Name to be used in the email subject line - we use Sailhouse here&lt;/span&gt;
  &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// url of your site&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="c1"&gt;// Email template with your brand's styling&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The email utility supports multiple providers with a simple abstraction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In email.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendConfirmationEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SendEmailOptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getEmailProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postmark&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="nf"&gt;sendWithPostmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resend&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="nf"&gt;sendWithResend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;default&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="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No email provider configured&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="kc"&gt;false&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;When users click the confirmation link, the &lt;code&gt;confirm.ts&lt;/code&gt; function handles the process and if it can decode the email, it publishes a confirmation event to Sailhouse&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sailhouse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CONFIRMATION_TOPIC&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changelog-confirmation&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;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;confirmed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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;Security is super important with confirmation flows. I've implemented HMAC-based tokens to make sure nobody can tamper with our confirmation links.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Create HMAC signature&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hmac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TOKEN_SECRET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Combine email and signature in the token&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenData&lt;/span&gt; &lt;span class="o"&gt;=&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;signature&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 combines the email address with a cryptographic signature, so we can verify it hasn't been tampered with when the user clicks the link. And since we're developers who appreciate good tools, I even included a &lt;a href="https://github.com/sailhouse/email-confirmation/blob/main/scripts/token-test.ts" rel="noopener noreferrer"&gt;utility script&lt;/a&gt; for testing token generation and verification during development.&lt;/p&gt;

&lt;p&gt;For storing confirmed subscribers, I'm leveraging Netlify Blob Store which gives us persistence without any database setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;blobStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setJSON&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="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;timestamp&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 code is designed to be easily customisable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Environment variables control email providers, sender information, and topics&lt;/li&gt;
&lt;li&gt;HTML and React templates can be modified to match your company's style for both the web pages and the email that is sent&lt;/li&gt;
&lt;li&gt;Sailhouse topics can be configured to fit your event schema&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Developer-Friendly Customisation Points
&lt;/h2&gt;

&lt;p&gt;Here's where it gets fun - this thing is designed to be hacked on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Super quick setup&lt;/strong&gt;: Clone the repo, connect to Netlify, setup a subscription from Sailhouse pointing to your site on Netlify, and you're literally done in under 5 minutes. Not even exaggerating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple config&lt;/strong&gt;: Just copy &lt;code&gt;.env.example&lt;/code&gt; and drop in your values. You can even change the event topic name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email templates&lt;/strong&gt;: They're just React with some basic props passed in. No fancy template engine to learn. Want to add your logo and brand colors? Just do it!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add your own logic&lt;/strong&gt;: The subscriber pattern makes it dead simple to extend once a confirmation has happened&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've added analytics tracking, CRM updates, and even a special welcome sequence for certain domains - all without touching the core code. That's the beauty of event-driven design.&lt;/p&gt;

&lt;p&gt;To demonstrate this, I also built in a summary reporting feature that sends updates to Slack. This is super handy for keeping track of new subscribers without having to log into another dashboard. The cool thing is this all happens automatically using a Sailhouse Cron job. You just set up a recurring event in Sailhouse that triggers this function to run daily, and boom - automated reporting!&lt;br&gt;
And want to switch email providers? Easy. The system supports both Postmark and Resend out of the box, and adding another is just a matter of implementing a single function that follows the pattern. Just change an environment variable to switch providers - no code changes needed.&lt;br&gt;
Oh, and if you're wondering how the Netlify integration works - it just does! The service automatically uses Netlify's context object to determine your site URL, so there's no configuration needed. One less thing to worry about.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Beyond Email Confirmation: Other Cool Stuff You Could Build
&lt;/h2&gt;

&lt;p&gt;Once you've got this pattern down, you can apply it to tons of other flows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two-factor auth flows&lt;/strong&gt;: "Enter the code we just sent you"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preference management&lt;/strong&gt;: "Click here to update your settings"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approval chains&lt;/strong&gt;: "Bob needs to approve this before it proceeds"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notification systems&lt;/strong&gt;: "Something happened that you care about"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The nicest part is how little code you need to adapt for these use cases. The event-driven pattern scales beautifully to different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Why We Built This At Sailhouse
&lt;/h2&gt;

&lt;p&gt;Totally honest moment - we built this for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dogfooding our own product&lt;/strong&gt;: Nothing tests a platform like actually using it. Yes I broke it. This is mandatory doing such things 🤣&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scratching our own itch&lt;/strong&gt;: We needed this for our projects anyway&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharing something useful&lt;/strong&gt;: Why not be helpful to others out there?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Showing off event-driven patterns&lt;/strong&gt;: It's surprisingly elegant for these flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning from real usage&lt;/strong&gt;: Building real things teaches you what matters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This wasn't some grand plan to create the ultimate email confirmation solution - it was just us solving a common problem in a way that made sense, then thinking "hey, others might find this useful too."&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Getting Started
&lt;/h2&gt;

&lt;p&gt;Want to try it out? It's super simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork the repo: &lt;a href="https://github.com/sailhouse/email-confirmation" rel="noopener noreferrer"&gt;github.com/sailhouse/email-confirmation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Set your environment variables&lt;/li&gt;
&lt;li&gt;Hit the &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; deploy button (or hook it up to your preferred serverless platform)&lt;/li&gt;
&lt;li&gt;Setup the push subscription on &lt;a href="https://sailhouse.dev/" rel="noopener noreferrer"&gt;Sailhouse&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;For bonus points, set up a Sailhouse Cron job to trigger daily summary reports:

&lt;ul&gt;
&lt;li&gt;Create a cron trigger in Sailhouse (e.g., daily at 9 AM)&lt;/li&gt;
&lt;li&gt;Have it publish to a topic that has a push subscription to your &lt;code&gt;/api/summary&lt;/code&gt; endpoint&lt;/li&gt;
&lt;li&gt;Configure the &lt;code&gt;CONFIRMATION_SUBSCRIPTION&lt;/code&gt; environment variable to point to your subscription&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Start using the events in your app&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Found a bug? Have an idea for improvement? PRs are absolutely welcome! This is a community tool, so help make it better.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Email confirmation isn't the most exciting part of your app, but it's one of those critical pieces of infrastructure that needs to be rock solid. This solution gives you a lightweight, scalable approach that you can set up in minutes and customise to your heart's content.&lt;/p&gt;

&lt;p&gt;The event-driven architecture makes it remarkably easy to extend, debug, and reason about - which means less time fighting with infrastructure and more time building the stuff that makes your product unique.&lt;/p&gt;

&lt;p&gt;Give it a spin, hack it to pieces, make it your own! And definitely let me know what you build with it - I love seeing how people extend and improve these patterns.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
