<?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: Phuc Nguyen</title>
    <description>The latest articles on DEV Community by Phuc Nguyen (@phucnd).</description>
    <link>https://dev.to/phucnd</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%2F4223921%2Fd5dbaf44-a64a-4ee1-a1d3-3f7603764c0a.jpg</url>
      <title>DEV Community: Phuc Nguyen</title>
      <link>https://dev.to/phucnd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/phucnd"/>
    <language>en</language>
    <item>
      <title>One domain, no mailbox seats: email for a one-person SaaS</title>
      <dc:creator>Phuc Nguyen</dc:creator>
      <pubDate>Tue, 03 Nov 2026 12:06:32 +0000</pubDate>
      <link>https://dev.to/phucnd/one-domain-no-mailbox-seats-email-for-a-one-person-saas-4hpf</link>
      <guid>https://dev.to/phucnd/one-domain-no-mailbox-seats-email-for-a-one-person-saas-4hpf</guid>
      <description>&lt;p&gt;You shipped your app over a few weekends with Cursor or Claude Code. Receipts and password resets already go out through some email API. Then the boring part shows up: you want &lt;code&gt;hello@&lt;/code&gt;, &lt;code&gt;team@&lt;/code&gt;, &lt;code&gt;sales@&lt;/code&gt; and &lt;code&gt;support@&lt;/code&gt; on your own domain. The usual answer is a mailbox per person in Google Workspace or Microsoft 365, plus a shared inbox tool for support, plus a forwarding rule that breaks every few months.&lt;/p&gt;

&lt;p&gt;For a one-person (or three-person) product, most of those mailboxes only ever &lt;strong&gt;receive&lt;/strong&gt; mail. You don't need a seat for that. This post shows a setup where one domain does four jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sends&lt;/strong&gt; transactional email from your app (a Resend-compatible API, or SMTP).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receives&lt;/strong&gt; mail for the whole domain and hands it to your code as a webhook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forwards&lt;/strong&gt; &lt;code&gt;team@&lt;/code&gt;, &lt;code&gt;sales@&lt;/code&gt;, &lt;code&gt;billing@&lt;/code&gt; to everyone's personal inbox with email groups.&lt;/li&gt;
&lt;li&gt;Puts &lt;strong&gt;&lt;code&gt;support@&lt;/code&gt;&lt;/strong&gt; straight into a support inbox, next to live chat, WhatsApp and Messenger, where replies go out from &lt;code&gt;support@&lt;/code&gt; itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The examples use &lt;a href="https://hatang.dev" rel="noopener noreferrer"&gt;Ha Tang&lt;/a&gt; (I work on it). Every step comes with the prompt to give your coding agent, the code it should produce, and what to double-check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup in one picture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                ┌── POST /emails (API or SMTP) ── receipts, resets, invoices
yourapp.com ────┤
                └── MX → Ha Tang ─┬── support@   → Support inbox (chat, WhatsApp, Messenger, email in one place)
                                  ├── team@, sales@, billing@ → email groups → members' own Gmail/Outlook
                                  └── anything else → email.received webhook → your code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 0: give your agent the real docs
&lt;/h2&gt;

&lt;p&gt;Agents get email integrations wrong mostly by guessing field names. Point them at the docs instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code: an MCP server that searches and reads the docs (no key needed for that)&lt;/span&gt;
claude mcp add hatang &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; @hatang/mcp

&lt;span class="c"&gt;# Any agent that reads AGENTS.md (Codex, Copilot, Cursor, Claude Code via CLAUDE.md)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://hatang.dev/docs/agents/AGENTS.md &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; AGENTS.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Cursor, add the same server to &lt;code&gt;.cursor/mcp.json&lt;/code&gt;, or paste &lt;code&gt;https://hatang.dev/docs/llms.txt&lt;/code&gt; into the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: verify the domain
&lt;/h2&gt;

&lt;p&gt;Add the domain under &lt;strong&gt;Email → Domains&lt;/strong&gt; (or &lt;code&gt;hatang.domains.create({ name: "yourapp.com" })&lt;/code&gt;) and publish the records it lists: DKIM, SPF and the bounce MX on a &lt;code&gt;send.&lt;/code&gt; subdomain, plus DMARC. Nothing here touches your existing mail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feu5aj9i9i7u6pgxm3hr5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feu5aj9i9i7u6pgxm3hr5.webp" alt="The domain page listing the DKIM, SPF, return-path MX and DMARC records to publish, with their status" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: send transactional email
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt; "Send an order confirmation with &lt;code&gt;@hatang/sdk&lt;/code&gt; when an order is paid. Read the Ha Tang sending guide first. Use the order id as the idempotency key. The key is in &lt;code&gt;HATANG_API_KEY&lt;/code&gt;."&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/email.ts (server only)&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;Hatang&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;@hatang/sdk&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;hatang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hatang&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// reads HATANG_API_KEY&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendOrderConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hatang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&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="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;Acme &amp;lt;orders@yourapp.com&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;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&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;reply_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;support@yourapp.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;// replies land in the support inbox (step 5)&lt;/span&gt;
      &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Order &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is confirmed`&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="s2"&gt;`&amp;lt;p&amp;gt;Thanks! Your order &amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/strong&amp;gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) is confirmed.&amp;lt;/p&amp;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="s2"&gt;`Thanks! Your order &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) is confirmed.`&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="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`order-confirmation-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// a retry never sends twice&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &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;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;The API follows Resend's shapes, so &lt;code&gt;react: &amp;lt;OrderEmail /&amp;gt;&lt;/code&gt; works with React Email, and moving existing Resend code is mostly a new base URL and key (&lt;a href="https://hatang.dev/docs/guides/email/migrate-from-resend" rel="noopener noreferrer"&gt;migration notes&lt;/a&gt;). Frameworks that only speak SMTP (Laravel, Django, WordPress, Nodemailer) can use &lt;code&gt;smtp.hatang.dev:5870&lt;/code&gt; with STARTTLS, user &lt;code&gt;hatang&lt;/code&gt;, password = your API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Double-check what the agent wrote:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The SDK is only imported in server code (route handlers, server actions, jobs), never in a client component.&lt;/li&gt;
&lt;li&gt;Every send that can be retried has an idempotency key derived from your own ids.&lt;/li&gt;
&lt;li&gt;The SDK returns &lt;code&gt;{ data, error }&lt;/code&gt; and does not throw for API errors: the agent must check &lt;code&gt;error&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: receive mail for the domain
&lt;/h2&gt;

&lt;p&gt;Turn &lt;strong&gt;Receiving&lt;/strong&gt; on for the domain. This points the domain's MX at Ha Tang, so do it on a domain (or subdomain) that doesn't already receive mail at Google or Microsoft. If your root domain stays on Workspace for now, use a subdomain such as &lt;code&gt;in.yourapp.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Every message to any address on the domain is stored and announced with an &lt;code&gt;email.received&lt;/code&gt; webhook. Your app can react to the ones it cares about, for example invoices sent to &lt;code&gt;bills@&lt;/code&gt;, or "reply to this email" threads from your users:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt; "Add &lt;code&gt;app/api/hatang/webhooks/route.ts&lt;/code&gt;: verify the Ha Tang webhook signature on the raw body with &lt;code&gt;constructWebhookEvent&lt;/code&gt;, and for &lt;code&gt;email.received&lt;/code&gt; sent to &lt;code&gt;bills@yourapp.com&lt;/code&gt;, fetch the message and its attachments and store them."&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/hatang/webhooks/route.ts&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;Hatang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;constructWebhookEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HatangSignatureError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EmailReceivedEvent&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;@hatang/sdk&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;hatang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hatang&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&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;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&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;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// verify the raw body, before any JSON parsing&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;event&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;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;constructWebhookEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;EmailReceivedEvent&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;raw&lt;/span&gt;&lt;span class="p"&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="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;HATANG_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="o"&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;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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HatangSignatureError&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="kc"&gt;null&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;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;throw&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email.received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bills@yourapp.com&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="kd"&gt;const&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="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hatang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;receiving&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&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="nx"&gt;files&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hatang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;receiving&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveBill&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="nx"&gt;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="na"&gt;text&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;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;files&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="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// your code&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="kc"&gt;null&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;200&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;strong&gt;Double-check:&lt;/strong&gt; agents love &lt;code&gt;await req.json()&lt;/code&gt; before verifying. The signature is computed over the exact bytes, so parse after &lt;code&gt;constructWebhookEvent&lt;/code&gt;, never before. Attachment &lt;code&gt;download_url&lt;/code&gt;s expire after an hour: copy the file if you need it later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: team@ and sales@ without mailbox seats
&lt;/h2&gt;

&lt;p&gt;Most shared addresses of a small team only need to &lt;strong&gt;reach people&lt;/strong&gt;. An email group is an address on your receiving domain whose mail is forwarded to a list of members, wherever their mailbox is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv15a5d91d6vde2nshsb8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv15a5d91d6vde2nshsb8.webp" alt="The Email groups page with team@, sales@ and billing@ on one domain, and the Create a group form" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create them in the dashboard (&lt;strong&gt;Email → Email groups&lt;/strong&gt;), or let your agent script it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&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="nx"&gt;domains&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hatang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domains&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&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;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;domains&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yourapp.com&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="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hatang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emailGroups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;domain_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;local_part&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sales&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sales&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;members&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;you@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;cofounder@gmail.com&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;What members get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each member gets their own copy. The sender shows as &lt;strong&gt;"Alice via Sales"&lt;/strong&gt; at &lt;code&gt;sales@yourapp.com&lt;/code&gt;, and &lt;strong&gt;Reply goes straight to Alice&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Members don't see each other's addresses. The original stays under &lt;strong&gt;Email → Received&lt;/strong&gt; and still fires &lt;code&gt;email.received&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Loops, spam, auto-replies and bounces are not forwarded, and one sender can't flood a group (30 messages an hour).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest trade-off: when a member replies, the reply comes from their &lt;strong&gt;own&lt;/strong&gt; address, not from &lt;code&gt;sales@&lt;/code&gt;. That's fine for vendors, partners, internal mail and most early sales conversations. When the conversation is with a &lt;strong&gt;customer&lt;/strong&gt; and should come from your domain, that's what the support inbox in the next step is for. Also note that every forwarded copy is a send: a message to a group of 3 uses 3 emails of your daily quota.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: support@ goes straight into the support inbox
&lt;/h2&gt;

&lt;p&gt;Connect &lt;code&gt;support@yourapp.com&lt;/code&gt; as an email channel under &lt;strong&gt;Customer Support → Channels → Add channel → Email&lt;/strong&gt;. Because the domain already receives mail at Ha Tang, there is nothing else to configure: no forwarding rule, no extra MX.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl8xsq6bqywpsymtwvumb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl8xsq6bqywpsymtwvumb.webp" alt="A support mailbox on a domain that already receives at Ha Tang: no forwarding needed, replies sent from the support address" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you get compared to a shared Gmail label:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One inbox for every channel.&lt;/strong&gt; Email threads sit next to website chat, the mobile SDKs, WhatsApp, Messenger, Instagram, Telegram, LINE, X, TikTok and Zalo, and the same customer is one profile across all of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replies come from &lt;code&gt;support@yourapp.com&lt;/code&gt;&lt;/strong&gt;, in the same thread, with delivery status on each reply (sent, delivered, opened, bounced).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;support+billing@&lt;/code&gt; works too&lt;/strong&gt;: the part after &lt;code&gt;+&lt;/code&gt; becomes a tag you can route on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The AI assistant answers email as well&lt;/strong&gt;, from the knowledge you give it, and hands over to you when it can't help.&lt;/li&gt;
&lt;li&gt;Acknowledgement emails, routing rules, tickets with SLA in business hours, and satisfaction ratings by email come with it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only &lt;code&gt;support@&lt;/code&gt; itself goes to the inbox. &lt;code&gt;team@&lt;/code&gt;, &lt;code&gt;sales@&lt;/code&gt; and every other address keep working as in steps 3 and 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes on the bill
&lt;/h2&gt;

&lt;p&gt;I won't quote prices because plans change, but here is what this setup removes for a small team:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A paid mailbox per person, mostly for shared addresses&lt;/td&gt;
&lt;td&gt;Members keep their personal inbox; shared addresses are groups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A shared-inbox or helpdesk tool for &lt;code&gt;support@&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;support@&lt;/code&gt; is a channel of the support inbox you already use for chat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forwarding rules and a third-party parser for inbound mail&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;email.received&lt;/code&gt; webhook on the same domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate vendors for transactional email and inbound&lt;/td&gt;
&lt;td&gt;One domain, one API key, one dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You may still want a real mailbox for yourself (for signing up to services, calendars, etc.). The point is that the shared addresses stop costing a seat each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch it work
&lt;/h2&gt;

&lt;p&gt;Every API call your keys make is logged with status, client and timing, which helps a lot when an agent wrote the integration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fso857uoz5opsb8m569ts.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fso857uoz5opsb8m569ts.webp" alt="The API request log: endpoint, status, method, API key, SDK and time for each call" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist before going live
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] DKIM, SPF and the bounce MX verified; DMARC published (start with &lt;code&gt;p=none&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;[ ] Receiving only on a domain or subdomain whose MX you control; not on a root domain still served by Workspace.&lt;/li&gt;
&lt;li&gt;[ ] No API key or webhook secret in client code or in a variable with a public prefix (&lt;code&gt;NEXT_PUBLIC_&lt;/code&gt;, &lt;code&gt;VITE_&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;[ ] Webhooks verified on the raw body; handlers return 200 quickly and are idempotent.&lt;/li&gt;
&lt;li&gt;[ ] Group members double-checked: everyone listed really should get every message to that address.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I'm on Resend / SendGrid / Postmark.&lt;/strong&gt; Sending code moves with a new base URL and key; the &lt;a href="https://hatang.dev/docs/guides/email/migrate-from-resend" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; maps the fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can my agent do this without me?&lt;/strong&gt; With &lt;code&gt;npx -y @hatang/mcp&lt;/code&gt; and an API key, an agent can send a test email, check whether your domain's DNS records are verified and read the docs as Markdown. Add &lt;code&gt;--read-only&lt;/code&gt; if you only want it to look things up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does the mail go out from?&lt;/strong&gt; Amazon SES, from your own verified domain, wherever your users are. Nothing in the setup above is tied to a country: any domain, any registrar, any DNS provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://hatang.dev/docs/guides/email/sending" rel="noopener noreferrer"&gt;Send email&lt;/a&gt; and &lt;a href="https://hatang.dev/docs/guides/email/smtp" rel="noopener noreferrer"&gt;SMTP relay&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hatang.dev/docs/guides/email/receiving" rel="noopener noreferrer"&gt;Receive email&lt;/a&gt; and &lt;a href="https://hatang.dev/docs/guides/email/groups" rel="noopener noreferrer"&gt;Email groups&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hatang.dev/help/channels/email-channel" rel="noopener noreferrer"&gt;Connect a support email address&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hatang.dev/docs/guides/getting-started/ai-agents" rel="noopener noreferrer"&gt;Use Ha Tang with AI coding agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A runnable starter: &lt;a href="https://github.com/sota-labs/hatang-nextjs-starter" rel="noopener noreferrer"&gt;hatang-nextjs-starter&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;I work on Ha Tang.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>email</category>
      <category>saas</category>
      <category>nextjs</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
