<?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: Josh Davis</title>
    <description>The latest articles on DEV Community by Josh Davis (@jdav-dev).</description>
    <link>https://dev.to/jdav-dev</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%2F3249503%2F0ccb06b8-1317-4522-bce9-5541749c1ba2.png</url>
      <title>DEV Community: Josh Davis</title>
      <link>https://dev.to/jdav-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jdav-dev"/>
    <language>en</language>
    <item>
      <title>Invisible Threads: Group email without the exposure</title>
      <dc:creator>Josh Davis</dc:creator>
      <pubDate>Sun, 08 Jun 2025 20:23:31 +0000</pubDate>
      <link>https://dev.to/jdav-dev/invisible-threads-group-email-without-the-exposure-24n6</link>
      <guid>https://dev.to/jdav-dev/invisible-threads-group-email-without-the-exposure-24n6</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;&lt;a href="https://invisiblethreads.jdav.dev/" rel="noopener noreferrer"&gt;Invisible Threads&lt;/a&gt; is a privacy-first group email solution. As the organizer, you create a thread from members' names and emails. Members reply to the thread like any other email. Replies are forwarded to all members using the thread's original sender address. Real email addresses stay hidden.&lt;/p&gt;

&lt;p&gt;You can use temporary threads for one-off events. For example, school fundraisers, sports seasons, or volunteer events. You can also keep threads going indefinitely. Members can opt out of individual threads with one click. Organizers can close a thread at any time. Closing a thread stops all future messages.&lt;/p&gt;

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

&lt;p&gt;Follow these steps to use the demo.  You will need a Postmark account.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new "Live" server &lt;a href="https://account.postmarkapp.com/servers/new" rel="noopener noreferrer"&gt;in Postmark&lt;/a&gt;.  Copy a "Server API token" for your new server.&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%2Fa5hffeol0bchapljedfx.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%2Fa5hffeol0bchapljedfx.png" alt="Postmark new server page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visit &lt;a href="https://invisiblethreads.jdav.dev/users/log-in" rel="noopener noreferrer"&gt;https://invisiblethreads.jdav.dev/users/log-in&lt;/a&gt;, paste your API token, and click "Log in →".&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%2F9f4l4f1m27fnpmp4we5h.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%2F9f4l4f1m27fnpmp4we5h.png" alt="Invisible Threads login page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click "+ New Thread" to create your first invisible thread.&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%2Fiia2iogl6syqr094jyoq.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%2Fiia2iogl6syqr094jyoq.png" alt="Invisible Threads index page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fill in "Sender Email Address" using a valid &lt;a href="https://account.postmarkapp.com/signature_domains" rel="noopener noreferrer"&gt;Sender Signature&lt;/a&gt; from your Postmark account.  "Email Thread Subject" sets the subject for all messages in the thread.  Fill in name and email address for at least two members.  Add more members as needed.&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%2Fc8ghvs43r9gfwbsazluf.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%2Fc8ghvs43r9gfwbsazluf.png" alt="New thread form"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click "Create Thread".  An introduction email will send to all members:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hello Alice,&lt;/p&gt;

&lt;p&gt;You've been added to an invisible thread - a group email conversation where replies are shared with all participants, but email addresses stay hidden.&lt;/p&gt;

&lt;p&gt;Participants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alice (you)&lt;/li&gt;
&lt;li&gt;Bob&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simply reply to this email as you normally would.  Addresses stay private, but anything you include in your message (like a signature) will be visible to others.&lt;br&gt;
For best results, reply from the same address that received this email.  Reply STOP to unsubscribe.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reply to the introduction email! Replies will forward to all other members.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you're done with a thread, close it.  Closing a thread will send the following email:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This invisible thread has been closed.  No further messages will be delivered or shared.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Individual members can unsubscribe from the thread.  If their email client supports it, members can one-click unsubscribe.  Alternatively, any reply starting with STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, REVOKE, OPTOUT, or QUIT (case-insensitive) on a line by itself will unsubscribe the member.  If all but one member unsubscribes, the thread is automatically closed.&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%2Ffotm29odstlixmwu4y22.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%2Ffotm29odstlixmwu4y22.png" alt="Closed thread with an unsubscribed participant"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you're done with the demo, feel free to delete your data, or keep using Invisible Threads if you find it useful!&lt;/p&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jdav-dev" rel="noopener noreferrer"&gt;
        jdav-dev
      &lt;/a&gt; / &lt;a href="https://github.com/jdav-dev/invisible_threads" rel="noopener noreferrer"&gt;
        invisible_threads
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Group email without the exposure
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Invisible Threads: Group email without the exposure&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This is a submission for the
&lt;a href="https://dev.to/challenges/postmark" rel="nofollow"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What I Built&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://invisiblethreads.jdav.dev/" rel="nofollow noopener noreferrer"&gt;Invisible Threads&lt;/a&gt; is a privacy-first group email solution
As the organizer, you create a thread from members' names and emails. Members reply to the thread
like any other email. Replies are forwarded to all members using the thread's original sender
address. Real email addresses stay hidden.&lt;/p&gt;
&lt;p&gt;You can use temporary threads for one-off events. For example, school fundraisers, sports seasons,
or volunteer events. You can also keep threads going indefinitely. Members can opt out of
individual threads with one click. Organizers can close a thread at any time. Closing a thread
stops all future messages.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Follow these steps to use the demo. You will need a Postmark account.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new "Live" server &lt;a href="https://account.postmarkapp.com/servers/new" rel="nofollow noopener noreferrer"&gt;in Postmark&lt;/a&gt;. Copy a
"Server API token" for your new server.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/priv/static/images/screenshots/create_server.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fpriv%2Fstatic%2Fimages%2Fscreenshots%2Fcreate_server.png" alt="Postmark new server page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visit &lt;a href="https://invisiblethreads.jdav.dev/users/log-in" rel="nofollow noopener noreferrer"&gt;https://invisiblethreads.jdav.dev/users/log-in&lt;/a&gt;…&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jdav-dev/invisible_threads" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Invisible Threads is built with &lt;a href="https://elixir-lang.org/" rel="noopener noreferrer"&gt;Elixir&lt;/a&gt;, &lt;a href="https://www.phoenixframework.org/" rel="noopener noreferrer"&gt;Phoenix&lt;/a&gt;, and most importantly, &lt;a href="https://postmarkapp.com/" rel="noopener noreferrer"&gt;Postmark&lt;/a&gt;.  Data lives on disk instead of a traditional database to keep the demo light.  Authentication uses Postmark API tokens, mapping each application user directly to a Postmark server. The whole thing is deployed to &lt;a href="https://fly.io/" rel="noopener noreferrer"&gt;Fly.io&lt;/a&gt;.  A minimal setup let me focus on Postmark's offerings.&lt;/p&gt;

&lt;p&gt;Threaded email conversations depend on specific headers.  I set the &lt;code&gt;Message-ID&lt;/code&gt; header on the introduction email to a known value.  On subsequent emails, I set the &lt;code&gt;In-Reply-To&lt;/code&gt; and &lt;code&gt;References&lt;/code&gt; headers to the same message ID.&lt;/p&gt;

&lt;p&gt;Emails go out with a &lt;code&gt;Reply-To&lt;/code&gt; header pointing at &lt;a href="https://postmarkapp.com/inbound-email" rel="noopener noreferrer"&gt;Postmark's inbound email processing&lt;/a&gt;.  Postmark allows the inbound email address to contain extra information after the mailbox.  I used this space to give each member a unique reply-to address per thread.  The format looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;postmark_mailbox&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;+&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;thread_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;member_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every email includes &lt;code&gt;List-Unsubscribe&lt;/code&gt; and &lt;code&gt;List-Unsubscribe-Post&lt;/code&gt; headers, following &lt;a href="https://postmarkapp.com/blog/list-unsubscribe-header" rel="noopener noreferrer"&gt;Postmark's guidelines&lt;/a&gt;.  Unsubscribe links are unique to each member and thread.&lt;/p&gt;

&lt;p&gt;I used Postmark's inbound email features three ways in this project.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To forward incoming email.&lt;/li&gt;
&lt;li&gt;To automatically handle unsubscribe requests from email-based (mailto:) &lt;code&gt;List-Unsubscribe&lt;/code&gt; headers.&lt;/li&gt;
&lt;li&gt;To automatically unsubscribe members who reply with an opt-out keyword.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first use case met my original idea going in.  The second two use cases came up during development.  In all cases, Postmark's inbound email was a perfect fit!&lt;/p&gt;

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