<?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: Volanea</title>
    <description>The latest articles on DEV Community by Volanea (@volanea).</description>
    <link>https://dev.to/volanea</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4222776%2Fecf2f2da-2ac0-4730-ac43-80d50768822c.png</url>
      <title>DEV Community: Volanea</title>
      <link>https://dev.to/volanea</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/volanea"/>
    <language>en</language>
    <item>
      <title>Webhooks or polling for inbound mail, and when to use both</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:26:25 +0000</pubDate>
      <link>https://dev.to/volanea/webhooks-or-polling-for-inbound-mail-and-when-to-use-both-pl</link>
      <guid>https://dev.to/volanea/webhooks-or-polling-for-inbound-mail-and-when-to-use-both-pl</guid>
      <description>&lt;p&gt;Webhooks feel modern and polling feels dated, but the right answer depends on what happens when a delivery is missed — and every webhook system misses deliveries eventually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks give latency, not guarantees
&lt;/h2&gt;

&lt;p&gt;A webhook is an HTTP request to your endpoint, retried a few times with backoff. If your service is down for longer than the retry window, or returns 500 consistently, the event is dropped unless the sender keeps a replayable log. Latency is excellent, guarantees are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polling gives completeness, not speed
&lt;/h2&gt;

&lt;p&gt;Polling an endpoint for messages since a cursor is boring and reliable: you control the pace, you can replay, and an outage costs you delay instead of data. The cost is a constant trickle of requests and a latency floor equal to the poll interval.&lt;/p&gt;

&lt;h2&gt;
  
  
  The combination that works
&lt;/h2&gt;

&lt;p&gt;Webhook for latency, poll as reconciliation. On each webhook, enqueue the message id; run a periodic poll that asks for anything newer than the last processed id and fills the gaps. The poll makes the system correct, the webhook makes it fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  One detail that makes reconciliation cheap
&lt;/h2&gt;

&lt;p&gt;Make the cursor the message id, not a timestamp. Timestamps collide at second granularity and drift between clocks, so a poll built on them either re-fetches or skips messages. A monotonic id gives you exact resume semantics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; delivers inbound mail by webhook and also exposes a cursor-based read endpoint, so reconciliation is a loop instead of a guess.&lt;/p&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>Where agent inboxes store mail is a product decision</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:20:12 +0000</pubDate>
      <link>https://dev.to/volanea/where-agent-inboxes-store-mail-is-a-product-decision-21am</link>
      <guid>https://dev.to/volanea/where-agent-inboxes-store-mail-is-a-product-decision-21am</guid>
      <description>&lt;p&gt;An agent inbox holds customer conversations, which means it holds personal data, which means somebody will eventually ask which country it lives in and how long it stays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Residency is per tenant, not per product
&lt;/h2&gt;

&lt;p&gt;Two customers can have incompatible requirements: one wants everything in the EU, another has no constraint but a strict retention limit. A single global bucket satisfies neither. The storage location has to be a property of the tenant, decided at creation and recorded somewhere an auditor can read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retention needs a clock per mailbox
&lt;/h2&gt;

&lt;p&gt;Deleting on a schedule means knowing what the schedule is for each mailbox, not for the product. Store the retention policy with the mailbox and run deletion from that, so changing the global default does not silently rewrite history for existing customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backups are part of the answer
&lt;/h2&gt;

&lt;p&gt;A deletion that leaves the data in a nightly backup has not deleted anything. Either expire backups on the same schedule or document the gap plainly, because the first audit question after 'do you delete it' is 'including backups'.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail that avoids a scramble
&lt;/h2&gt;

&lt;p&gt;Attachments are the part everyone forgets. If the body is in the region and the attachments are in a shared global bucket, residency is not satisfied and the answer to the questionnaire is wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; attaches storage and retention settings to each inbox, so residency follows the tenant instead of the deployment.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>product</category>
      <category>saas</category>
    </item>
    <item>
      <title>Reading DSN bounces correctly</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:19:47 +0000</pubDate>
      <link>https://dev.to/volanea/reading-dsn-bounces-correctly-3ehn</link>
      <guid>https://dev.to/volanea/reading-dsn-bounces-correctly-3ehn</guid>
      <description>&lt;p&gt;A bounce is not a sentence, it is a structured report: a delivery status notification with a status code, a reporting MTA, and the original headers attached. Parsing the human text is the reason bounce handling is unreliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fields that matter
&lt;/h2&gt;

&lt;p&gt;message/delivery-status carries per-recipient blocks with Action (failed, delayed, delivered, relayed), Status (the three-digit enhanced code) and Diagnostic-Code. A 5.1.1 means unknown recipient; a 4.2.2 means mailbox full; a 5.7.1 usually means the receiver blocked you. Those three lead to completely different actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The original message id is in there
&lt;/h2&gt;

&lt;p&gt;The third part of a DSN is the original message headers. That is how you correlate a bounce with the send that caused it — not by matching the recipient address, which may appear in several campaigns. Without the correlation, a bounce cannot update the right record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delayed is not failed
&lt;/h2&gt;

&lt;p&gt;Action: delayed arrives hours before a failure and after the message eventually succeeds. Treating it as a bounce suppresses valid addresses and makes the dashboard look worse than reality. Store it as a state, not as a failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  One detail that prevents a support loop
&lt;/h2&gt;

&lt;p&gt;Keep the Diagnostic-Code verbatim. When a customer forwards a bounce, the remote server's sentence is the only thing that distinguishes a full mailbox from a blocklist, and your own classification will not carry that nuance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; parses DSNs into action and status per recipient and keeps the raw diagnostic next to the original send.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>software</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Your SMTP connection pool is probably leaking</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:12:56 +0000</pubDate>
      <link>https://dev.to/volanea/your-smtp-connection-pool-is-probably-leaking-39h8</link>
      <guid>https://dev.to/volanea/your-smtp-connection-pool-is-probably-leaking-39h8</guid>
      <description>&lt;p&gt;A worker opens a connection, sends, and forgets to close it. Nothing breaks for a week, then the provider starts refusing new connections and every send fails at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connections are the scarce resource
&lt;/h2&gt;

&lt;p&gt;Providers cap concurrent connections per account and per IP, often at a low number. A pool that grows because closed connections are not returned will hit that cap, and the failure mode is total rather than gradual: once the cap is reached, every new send blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the leak comes from
&lt;/h2&gt;

&lt;p&gt;An exception between MAIL FROM and QUIT skips the close. A timeout on DATA leaves the socket open until the OS reaps it. A retry path that creates a fresh connection without releasing the failed one doubles the count. None of these show up in a happy-path test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Wrap the whole conversation in a context manager that guarantees QUIT, and on any exception close the socket rather than returning it to the pool — a connection in an unknown protocol state is worse than no connection. Then export the pool size as a metric and alert when it approaches the provider's cap.&lt;/p&gt;

&lt;h2&gt;
  
  
  One detail worth copying
&lt;/h2&gt;

&lt;p&gt;Reuse connections for multiple messages to the same domain, but reset state with RSET between recipients. Reconnecting for every message is slow and looks like abuse to the receiver; not resetting leaks the previous envelope into the next send.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; manages the SMTP session per domain and exposes connection and deferral counters next to the send log.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>devops</category>
    </item>
    <item>
      <title>Greylisting is a 4xx you should not panic about</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:12:30 +0000</pubDate>
      <link>https://dev.to/volanea/greylisting-is-a-4xx-you-should-not-panic-about-1bmi</link>
      <guid>https://dev.to/volanea/greylisting-is-a-4xx-you-should-not-panic-about-1bmi</guid>
      <description>&lt;p&gt;A first-time sender gets a temporary rejection from a server it has never talked to before, and the message is delayed by minutes. That is greylisting, and it is the single most misdiagnosed response in transactional email.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the server is doing
&lt;/h2&gt;

&lt;p&gt;It records the triplet of sender IP, envelope sender and recipient, answers with a 4xx at RCPT or DATA, and waits. A real mail server retries after a delay; a spam bot usually does not. When the retry arrives after the configured window, typically a few minutes, the triplet is on the allow list and the message goes through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the first message to a domain is special
&lt;/h2&gt;

&lt;p&gt;Greylisting hits new triplets, so your very first message to a new recipient domain is the one that gets delayed. The second is fine. That means a signup confirmation to a never-contacted domain can arrive minutes late through no fault of yours, and the fix is to retry rather than to re-send a new message with a new id — the new id is a new triplet, and it gets greylisted again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do
&lt;/h2&gt;

&lt;p&gt;Treat 4xx as retryable with an interval longer than the typical greylist window, keep the same message id and the same envelope sender, and cap total attempts. Do not reset the message, do not switch provider mid-flight, and do not mark the address as invalid.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail that matters
&lt;/h2&gt;

&lt;p&gt;Log the triplet and the retry timing. When a customer says their verification email took eight minutes, 'greylisted at the recipient, delivered on attempt two' is a complete and correct answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; retries with a stable message id and surfaces the remote 4xx text, so a greylisting delay is visible instead of mysterious.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>infrastructure</category>
      <category>networking</category>
    </item>
    <item>
      <title>List-Unsubscribe needs both headers and a working endpoint</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:06:17 +0000</pubDate>
      <link>https://dev.to/volanea/list-unsubscribe-needs-both-headers-and-a-working-endpoint-2knj</link>
      <guid>https://dev.to/volanea/list-unsubscribe-needs-both-headers-and-a-working-endpoint-2knj</guid>
      <description>&lt;p&gt;Bulk senders at the large providers are expected to support one-click unsubscribe, and the implementation is a pair of headers plus an endpoint that actually works without a login.&lt;/p&gt;

&lt;h2&gt;
  
  
  The header pair
&lt;/h2&gt;

&lt;p&gt;List-Unsubscribe carries the URL (or mailto) that a client can use. List-Unsubscribe-Post with the value List-Unsubscribe=One-Click tells the client it may POST instead of opening a page. The client sends a POST with that body to the URL, and the server must act on it immediately and answer 200.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes wrong
&lt;/h2&gt;

&lt;p&gt;The URL points at a page that requires a session, so the POST gets a redirect to a login screen and the unsubscribe silently fails. The client reports the message as spam instead, which is worse for you than an unsubscribe. The endpoint must be public, idempotent, and fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope
&lt;/h2&gt;

&lt;p&gt;One-click unsubscribe applies to the stream the message came from. If the same address also receives receipts, those must keep flowing — a blanket suppression breaks the product. Encode the stream in the signed token and honour it precisely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail that keeps you compliant
&lt;/h2&gt;

&lt;p&gt;Store the unsubscribe even if the message is later bounced, and make the suppression check part of the send path rather than a nightly job. A campaign that starts before the job runs sends to people who already opted out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; emits both headers per stream and applies suppression at delivery time, so a one-click unsubscribe takes effect on the next send.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The no-reply address is a choice, and usually the wrong one</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:05:51 +0000</pubDate>
      <link>https://dev.to/volanea/the-no-reply-address-is-a-choice-and-usually-the-wrong-one-552j</link>
      <guid>https://dev.to/volanea/the-no-reply-address-is-a-choice-and-usually-the-wrong-one-552j</guid>
      <description>&lt;p&gt;do-not-reply@ is a rejection of a customer's reply, and receivers increasingly treat that rejection as a signal: a message nobody can answer is closer to a broadcast than to a conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes when replies are allowed
&lt;/h2&gt;

&lt;p&gt;A monitored address turns an outbound message into a thread. Replies arrive with context, and the relevant team can answer. From the receiver's side, a domain whose mail generates real replies has demonstrably engaged recipients, which is the same signal warmup is trying to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you genuinely cannot read replies
&lt;/h2&gt;

&lt;p&gt;Then say so in the body and still accept the mail. A no-reply address that bounces produces a backscatter problem: the customer's reply bounces to them, they reply again, and you have created a loop with a person. Accept and drop is better than reject.&lt;/p&gt;

&lt;h2&gt;
  
  
  The middle ground most teams land on
&lt;/h2&gt;

&lt;p&gt;Accept replies into a queue that is triaged, and auto-acknowledge with a link to the right channel. The customer is not talking to a wall, and the team is not drowning.&lt;/p&gt;

&lt;h2&gt;
  
  
  One detail worth setting
&lt;/h2&gt;

&lt;p&gt;If you do use an unmonitored address, set a Reply-To that goes somewhere real. The customer's client will use Reply-To over From, and the reply lands where a human can see it instead of vanishing into a mailbox nobody opens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; routes replies into a real inbox with threading, so a transactional message can start a conversation instead of ending one.&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Return-Path and From are different fields and receivers read both</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 05:59:38 +0000</pubDate>
      <link>https://dev.to/volanea/return-path-and-from-are-different-fields-and-receivers-read-both-1i87</link>
      <guid>https://dev.to/volanea/return-path-and-from-are-different-fields-and-receivers-read-both-1i87</guid>
      <description>&lt;p&gt;A message carries two identities: the From header the customer sees, and the envelope sender the receiving server uses for bounces and SPF. Aligning them badly is a common cause of mail landing in spam with no obvious error.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each field controls
&lt;/h2&gt;

&lt;p&gt;SPF checks the envelope sender, which is the domain after MAIL FROM during the SMTP conversation and shows up as Return-Path in the delivered message. DKIM signs parts of the message and is validated against the d= domain in the signature. DMARC requires that at least one of them aligns with the From header domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The common misconfiguration
&lt;/h2&gt;

&lt;p&gt;Sending as From: &lt;a href="mailto:hello@example.com"&gt;hello@example.com&lt;/a&gt; while the envelope is &lt;a href="mailto:bounce@provider.com"&gt;bounce@provider.com&lt;/a&gt;. SPF passes for provider.com, DKIM may pass for provider.com, and DMARC fails because neither aligns with example.com. The fix is to sign with your own domain and use a return-path subdomain you control, or to use the provider's domain for both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the bounce address matters operationally
&lt;/h2&gt;

&lt;p&gt;Bounces go to the envelope sender, not to From. If the envelope is a black hole, you never learn that a mailbox is dead, and you keep sending to it. Use a real mailbox for the return-path, or one that your provider processes and reports back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check to run once
&lt;/h2&gt;

&lt;p&gt;Send a message to a Gmail address you control, open the original, and look at Authentication-Results. Both spf and dkim should say pass, and the header should show the same domain you put in From.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; sets the return-path per sending domain and reports SPF, DKIM and DMARC results together, so misalignment shows up before a customer reports it.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>cybersecurity</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Testing email flows in CI without sending email</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 05:59:13 +0000</pubDate>
      <link>https://dev.to/volanea/testing-email-flows-in-ci-without-sending-email-4ge3</link>
      <guid>https://dev.to/volanea/testing-email-flows-in-ci-without-sending-email-4ge3</guid>
      <description>&lt;p&gt;Email is usually the least tested part of a product because the test would send a real message to a real inbox. The workaround is a test doubles pattern that is easy to get wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assert on the intent, not the transport
&lt;/h2&gt;

&lt;p&gt;Most suites mock the email client and assert that send was called. That passes while the template is broken. The useful assertion is on the rendered output: subject, recipient, and the presence of the token or link that the flow depends on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render in the test
&lt;/h2&gt;

&lt;p&gt;Render the template with the same code path as production, then check the HTML and the text part both contain the expected URL. This catches the failure that matters most: a link that lost its base URL and now points at localhost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep one real send per release
&lt;/h2&gt;

&lt;p&gt;Mocks cannot catch DNS, authentication, or provider rejection. One real send per release to an internal address, with the headers checked for dkim=pass and a matching return-path, covers the gap that unit tests structurally cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail that finds regressions
&lt;/h2&gt;

&lt;p&gt;Snapshot the rendered message body and diff it in CI. Template edits that break variable substitution show up as a diff instead of as a customer complaint, and the diff is small enough to review.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; exposes a render endpoint that returns the exact subject and body for given variables, so tests can assert on the real output.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>cicd</category>
      <category>softwareengineering</category>
      <category>testing</category>
    </item>
    <item>
      <title>Storing email bodies without building a data swamp</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 05:53:00 +0000</pubDate>
      <link>https://dev.to/volanea/storing-email-bodies-without-building-a-data-swamp-1bnn</link>
      <guid>https://dev.to/volanea/storing-email-bodies-without-building-a-data-swamp-1bnn</guid>
      <description>&lt;p&gt;Email is the largest unstructured dataset most products own, and the default approach — dump the whole raw message into a database column — becomes a problem at around a hundred thousand messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Split the record
&lt;/h2&gt;

&lt;p&gt;Store the metadata in the database: message id, thread id, participants, subject, timestamps, direction, and a pointer to the body. Store the raw message and the rendered parts in object storage keyed by message id. Listing an inbox then reads small rows, and opening one message reads one object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skew the schema
&lt;/h2&gt;

&lt;p&gt;Bodies are large, variable, and rarely queried. Metadata is small, uniform, and queried constantly. Putting them in the same table means every index carries the weight of the part you never search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retention has to be per mailbox
&lt;/h2&gt;

&lt;p&gt;Support conversations and agent inboxes have different legal expectations. If retention is a global setting, someone will eventually ask you to delete one conversation and you will have to write a migration instead of a delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail that makes search work
&lt;/h2&gt;

&lt;p&gt;Index the extracted text, not the HTML. Indexing raw HTML puts tag names in your search results and multiplies the index size for no benefit. Extract once at ingest, store the extracted text next to the metadata, and search that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; keeps message metadata queryable and bodies addressable, so inbox listing stays fast as a mailbox grows.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>database</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>What actually changes during a domain warmup</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 05:52:35 +0000</pubDate>
      <link>https://dev.to/volanea/what-actually-changes-during-a-domain-warmup-5f2g</link>
      <guid>https://dev.to/volanea/what-actually-changes-during-a-domain-warmup-5f2g</guid>
      <description>&lt;p&gt;Warmup advice is usually a table of volumes by day, which is the least interesting part. What changes is the receiver's confidence, and confidence comes from engagement, not from the calendar.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signal receivers are watching
&lt;/h2&gt;

&lt;p&gt;For a new domain, the first few hundred messages are a sample. If recipients open, reply, and move messages out of the spam folder, the receiver learns that mail from this domain is wanted and starts putting it in the inbox. If the sample is ignored or marked as spam, the same volume produces the opposite result. Volume without engagement is just faster failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send to the people who asked
&lt;/h2&gt;

&lt;p&gt;Warm up with your most engaged segment: recent signups, active users, people who clicked something last week. Those are the recipients most likely to engage, which is exactly what the warmup needs. Sending your launch announcement to an old list during warmup teaches receivers the wrong lesson about your domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the content boring
&lt;/h2&gt;

&lt;p&gt;Plain, expected mail during warmup: receipts, confirmations, password resets. No images with short links, no subject lines that look like marketing. You are establishing a baseline, not testing creative.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement that matters
&lt;/h2&gt;

&lt;p&gt;Track inbox placement per provider, not aggregate open rate. A 40 percent open rate that is 90 percent Gmail and 5 percent Outlook is a warning, not a win.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; reports per-domain authentication and delivery outcomes separately, which is the only way to see one provider falling behind while the average looks fine.&lt;/p&gt;

</description>
      <category>marketing</category>
      <category>startup</category>
    </item>
    <item>
      <title>Letting a human take over a thread an agent started</title>
      <dc:creator>Volanea</dc:creator>
      <pubDate>Tue, 03 Nov 2026 05:46:22 +0000</pubDate>
      <link>https://dev.to/volanea/letting-a-human-take-over-a-thread-an-agent-started-1al3</link>
      <guid>https://dev.to/volanea/letting-a-human-take-over-a-thread-an-agent-started-1al3</guid>
      <description>&lt;p&gt;The worst support experience is an agent replying over a human, or a human replying while the agent is mid-draft. Both happen because nothing in the system owns the conversation at any given moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ownership is a state machine
&lt;/h2&gt;

&lt;p&gt;A conversation is agent-owned, human-owned, or paused. Only one actor may send at a time. A takeover is an explicit transition with a timestamp and an operator id, not a flag somebody toggles in an admin panel while the agent is already generating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check ownership immediately before sending
&lt;/h2&gt;

&lt;p&gt;Optimistic concurrency: read the owner, compose, then send only if the owner is still the same. If a human claimed the thread while the agent was writing, discard the draft instead of sending it. This one check removes the double-reply failure entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hand back deliberately
&lt;/h2&gt;

&lt;p&gt;When the human is done, the handback should re-arm the agent with context — the messages sent while it was paused — otherwise the agent answers the customer's first message again with stale assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail worth copying
&lt;/h2&gt;

&lt;p&gt;Write a timeline entry for every ownership change. When a customer complains about a contradictory answer, the timeline shows who said what and when, which is the difference between a fix and an argument.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://volanea.com" rel="noopener noreferrer"&gt;Volanea&lt;/a&gt; stores per-inbox thread state server side, so ownership is a fact both the agent and the human read before sending.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
