<?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: Jed Nelson</title>
    <description>The latest articles on DEV Community by Jed Nelson (@jedsonofnel).</description>
    <link>https://dev.to/jedsonofnel</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%2F3210900%2Fd2f13108-de97-4baa-aebf-1185ed7fb553.png</url>
      <title>DEV Community: Jed Nelson</title>
      <link>https://dev.to/jedsonofnel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jedsonofnel"/>
    <language>en</language>
    <item>
      <title>Turn 'Hello' Emails into Beautiful Artist Profiles with AI (and Postmark!)</title>
      <dc:creator>Jed Nelson</dc:creator>
      <pubDate>Mon, 09 Jun 2025 05:51:34 +0000</pubDate>
      <link>https://dev.to/jedsonofnel/turn-hello-emails-into-beautiful-artist-profiles-with-ai-and-postmark-3a5j</link>
      <guid>https://dev.to/jedsonofnel/turn-hello-emails-into-beautiful-artist-profiles-with-ai-and-postmark-3a5j</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built what I'm calling the "Artist Draft" feature for our online art marketplace (&lt;a href="https://otterkin.co.uk" rel="noopener noreferrer"&gt;Otterkin&lt;/a&gt;). This allows interested prospective artists to easily create a stunning interactive draft profile so they can get excited about signing up when we launch.&lt;/p&gt;

&lt;p&gt;Our goal at Otterkin is to build a vibrant community of talented artists and we realised that exchanging email addresses is the most natural way for artists to connect with us. So, we leveraged this interaction to create a more engaging onboarding experience. When an artist sends an introductory email to &lt;a href="mailto:introduction@inbound.otterkin.co.uk"&gt;introduction@inbound.otterkin.co.uk&lt;/a&gt;, our system springs into action.&lt;/p&gt;

&lt;p&gt;It all begins when Postmark delivers the email contents to our Rails Action Mailbox webhook. From there, a new ArtistDraft model is created. Crucially, we use Google's Gemini AI and its structured output feature to intelligently extract relevant information from the email into JSON and populate the required ArtistDraft fields.  Given it's meant to be a draft, the LLM is instructed to make up reasonable sounding guesses for anything not obvious in the email, and so this is a perfect use case for generative AI!&lt;/p&gt;

&lt;p&gt;This ArtistDraft then powers a dynamic, glossy "fake" Artist Profile page - think an Airbnb listing but for artists. A link to this page is sent back to the artist so they can instantly visualize what their public profile on the Otterkin platform could look like, making the onboarding process much more tangible and exciting than just mockups or screenshots.  At the moment the main call to action is for the artist to join our newsletter or "waitlist", but after launch this could easily be the start of the fully fledged onboarding process to making a real artist profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Try sending an email to &lt;a href="mailto:introduction@inbound.otterkin.co.uk"&gt;introduction@inbound.otterkin.co.uk&lt;/a&gt;!  I've had far too much fun coming up with the most unhinged artist introductions imaginable and seeing the end result cracks me up.  Unless you are serious artist with serious artist thoughts and serious artist places to be, I'd so encourage you to send something slightly bizarre.  Here's an example of something I concocted earlier:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fw2bg7n4vfzh6u5krmxno.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fw2bg7n4vfzh6u5krmxno.png" alt="Screenshot of an email sent to the introduction mailbox" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4y48me3zilof4f86954p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4y48me3zilof4f86954p.png" alt="Screenshot of the automatic response email" width="800" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F95lxx0zgllnui1wnsslf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F95lxx0zgllnui1wnsslf.png" alt="Screenshot of the created draft page" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you have as much fun as I do!  There's definitely scope for ratcheting up the silliness by using Gemini to create some fake artwork to put in the gallery but I've not implemented that yet to save on API costs.  I wonder what my email inspired art (from the screenshots above) would look like?  Sadly we may never know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;

&lt;p&gt;I have forked and gutted our rails app leaving not much beyond anything relevant to our inbound feature &lt;a href="https://github.com/Jedsonofnel/otterkin-web-inbound-showcase" rel="noopener noreferrer"&gt;Here&lt;/a&gt;, the README should help you take a tour of the highlights!&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The app is built with Rails which is always such a treat to work with.  The steps I took (roughly in order) to build this were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement and write tests for the GeminiClient, which had to be handmade as I couldn't find a good library that supported structured output.&lt;/li&gt;
&lt;li&gt;Make all the ArtistDraft resource stuff (controllers, model, view etc) where the model is responsible for calling the Gemini client (and handling all the errors my word).&lt;/li&gt;
&lt;li&gt;Writing the ArtistDraftMailer and making the Postmark templates I needed.&lt;/li&gt;
&lt;li&gt;Writing the IntroductionsMailbox which handles the incoming email from Postmark.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Originally, as I was learning how the inbound email API worked, I didn't know about Rails' Action Mailbox.  I used a normal #create action on my ArtistDraftsController to use as a webhook and I set about trying to make it work.  I spent ages creating mock json files for various types of inbound email to use in testing then wrote a fair amount of code for parsing it properly.  This all felt like a giant faff.  I then read in the Postmark docs about how it would be a good idea to implement basic auth for the webhook and I did a bit of basic googling about how I'd implement that when, that fateful evening, I discovered the ActionMailbox!&lt;/p&gt;

&lt;p&gt;I'd been quietly worried about what would happen if I wanted to add another inbound feature and how I'd route different emails around my app with my existing controller-based approach and so to find out that was a solved problem felt incredible.  I may have wept.  Scrolling through the &lt;a href="https://guides.rubyonrails.org/action_mailbox_basics.html" rel="noopener noreferrer"&gt;Action Mailbox Guide&lt;/a&gt; was another incredible experience when I saw that Postmark integration was already built and wonderful and so all that frustration of my previous integration was soon overcome by Rails-y joy and it only took me an evening to replace everything with a mailbox.  Behold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IntroductionsMailbox&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationMailbox&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;
    &lt;span class="n"&gt;sender_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="n"&gt;text_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_email_body&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;handle_existing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="vi"&gt;@artist_draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ArtistDraft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email_address: &lt;/span&gt;&lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@artist_draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_from_email_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;text_body: &lt;/span&gt;&lt;span class="n"&gt;text_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;notify_gemini_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gemini_processing_failed?&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;notify_not_intro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vi"&gt;@artist_draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;introduction&lt;/span&gt;

    &lt;span class="n"&gt;attach_artworks_to_draft&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@artist_draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="no"&gt;ArtistDraftMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;artist_draft: &lt;/span&gt;&lt;span class="vi"&gt;@artist_draft&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt; &lt;span class="s2"&gt;"Failed to create ArtistDraft for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Errors: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@artist_draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sentence&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All-in-all, email features that I had felt were quite daunting turned out to be super doable.  Big fan.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
  </channel>
</rss>
