← Blog

Send your Hugo blog as a newsletter with Emit — from absolute scratch

Guide May 29, 2026 · 12 min read

You write in Hugo, run hugo, and push static files. There's no database, no login, no server to babysit — and no obvious way to email readers when you publish. This guide fixes that. We'll start with nothing and end with a working loop: every time you ship a post, Emit picks it up from your RSS feed and emails your confirmed subscribers, from your own domain. You pay $0.80 per 1,000 emails — no monthly fee, no per-subscriber tier.

What we'll do

  1. Prerequisites & what it costs
  2. Create your Emit account
  3. Verify your sending domain
  4. Fund your account with prepaid credits
  5. Make sure Hugo publishes a usable RSS feed
  6. Connect the feed to Emit
  7. Add a subscribe form to your Hugo site
  8. Test the whole loop end to end
  9. Publish a post and watch it send

Before you start

You'll need three things:

Everything below can be done from the dashboard with no code, or from the API with curl. We'll show both. Set a shell variable for the API base so the commands copy-paste cleanly:

export EMIT=https://api.rssemit.com

Step 1Create your Emit account

The fastest path is the dashboard. Open rssemit.com/app.html, choose Create account, and enter your name and email. You'll be handed an API key — copy it now, because it's shown exactly once and stored only as a hash on our side. If you lose it, you rotate it; you can't retrieve it.

Prefer the API? It's a single call:

curl -sX POST $EMIT/v1/accounts \
  -H "Content-Type: application/json" \
  -d '{"name":"My Hugo Blog","email":"you@yourblog.com"}'

The response includes your key once:

{
  "id": "acct_3pQ…",
  "object": "account",
  "email": "you@yourblog.com",
  "public_token": "pk_live_8f2…",
  "api_key": "sk_live_a91c…",   <-- copy this now, shown once
  "credit_balance": 0
}

Stash the key and your public token in environment variables — we'll use both:

export EMIT_KEY=sk_live_a91c…
export EMIT_PK=pk_live_8f2…

Emit will email you a verification link for the account address — click it to confirm you own the inbox. That's separate from verifying your sending domain, which is next.

Authentication, in one line. Every authenticated request carries Authorization: Bearer $EMIT_KEY. The pk_… public token is different — it's safe to ship in your site's HTML and can only create pending (opt-in) subscribers, nothing else.

Step 2Verify your sending domain

To send from posts@yourblog.com you have to prove you own yourblog.com and let the mail provider sign your messages (DKIM). Register the domain with Emit to get the exact DNS records to add:

curl -sX POST $EMIT/v1/domains \
  -H "Authorization: Bearer $EMIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"yourblog.com"}'

The response (and the dashboard's Domains section) lists the records to create at your DNS host — typically:

Add them in your registrar / DNS dashboard (Cloudflare, Namecheap, Route 53, etc.). DNS can take a few minutes to propagate, then ask Emit to verify:

curl -sX POST $EMIT/v1/domains/{domain_id}/verify \
  -H "Authorization: Bearer $EMIT_KEY"

When it reports verified, any from address on that domain is yours to use. If verification fails, it's almost always propagation — wait a few minutes and retry.

Step 3Fund your account

Emit bills per email from a prepaid balance — zero balance means nothing sends, so you load credits before connecting a feed. Start a top-up (minimum $10):

curl -sX POST $EMIT/v1/billing/topup \
  -H "Authorization: Bearer $EMIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount_usd":10}'

You get back a Stripe Checkout URL:

{
  "checkout_url": "https://checkout.stripe.com/c/pay/…",
  "amount_usd": 10,
  "credits": 12500
}

Open that URL, pay with a card, and the Stripe webhook credits your balance — 12,500 emails for $10. From the dashboard this is just the Billing → Add credits button; same Checkout page, no copy-paste. You can confirm the balance landed any time:

curl -s $EMIT/v1/account -H "Authorization: Bearer $EMIT_KEY"

How far does $10 go? Credits are charged per email sent, not per subscriber stored. If you have 500 confirmed readers and publish 4 posts a month, that's 500 × 4 = 2,000 emails — about $1.60/month. The other $8.40 rolls forward. If you publish nothing, you're charged nothing.

Step 4Make sure Hugo publishes a usable RSS feed

Good news: Hugo generates RSS automatically. By default your home feed lives at /index.xml (and each section gets its own, e.g. /posts/index.xml). Confirm RSS output is enabled for the home page in your hugo.toml (or config.toml):

[outputs]
  home = ["HTML", "RSS"]
  section = ["HTML", "RSS"]

Run a build and check the feed exists:

hugo
curl -s https://yourblog.com/index.xml | head -40

You should see <rss> with one <item> per post. One thing worth deciding now: Hugo's default RSS template ships the post summary, not the full body, inside <description>. That means readers get an excerpt and click through to your site. If you'd rather email the full post, override the RSS template to emit the full content. Create layouts/_default/rss.xml and change the description line to use .Content:

<description>{{ .Content | html }}</description>

(Copy Hugo's built-in RSS template as a starting point and tweak just that line.) Either way works with Emit — it's your call whether the email is a teaser or the whole thing. Rebuild and re-deploy so the live feed reflects your choice.

Step 5Connect the feed to Emit

Now point Emit at that feed and tell it which address to send from. In the dashboard: Feeds → Add feed, paste your feed URL, set the from-address, pick a schedule. Or via the API:

curl -sX POST $EMIT/v1/feeds \
  -H "Authorization: Bearer $EMIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url":      "https://yourblog.com/index.xml",
    "from":     "posts@yourblog.com",
    "schedule": "on_publish"
  }'

A few notes on the fields:

Emit immediately does a baseline poll and records the items already in your feed without emailing them — so connecting a feed never blasts your whole archive. From here on, only genuinely new items send. Conditional GET (ETag / Last-Modified) is on by default, so polling is cheap and polite to your host.

Step 6Add a subscribe form to your Hugo site

A feed with no subscribers sends nothing. Emit collects readers through a double opt-in form that posts straight to the public endpoint — no API key in your HTML, no backend. Because Hugo is just templates, this is a partial.

Create layouts/partials/subscribe.html:

<form action="https://api.rssemit.com/v1/public/subscribe" method="post"
      style="max-width:380px">
  <input type="hidden" name="token" value="pk_live_8f2…">
  <input type="hidden" name="redirect" value="https://yourblog.com/subscribed/">
  <!-- honeypot: bots fill this, humans don't -->
  <input type="text" name="hp" tabindex="-1" autocomplete="off"
         style="position:absolute;left:-9999px;" aria-hidden="true">
  <label>Subscribe to the newsletter</label>
  <input type="email" name="email" required placeholder="you@example.com">
  <button type="submit">Subscribe</button>
  <a href="https://rssemit.com?ref=embed">powered by emit</a>
</form>

Replace pk_live_8f2… with your own public token (the pk_… value from Step 1, also shown in the dashboard's Embed section, which generates this snippet pre-filled). Point redirect at any "thanks, check your inbox" page on your own site.

Then drop the form wherever you want it — your footer partial, the homepage, a dedicated page:

{{ partial "subscribe.html" . }}

That's the whole integration. The form is plain HTML, so it works without JavaScript and survives any theme. It includes a honeypot field, and the endpoint is rate-limited, so casual bots are filtered out. Rebuild and deploy.

Why double opt-in? When someone submits the form, Emit creates them as pending and emails a confirmation link. They aren't added to your list — and you aren't billed to mail them — until they click it. That keeps your list clean and your sender reputation intact.

Step 7Test the whole loop

Before you tell the world, prove it works with your own address:

  1. Visit your site, enter your email in the subscribe form, submit.
  2. Check your inbox for the confirmation email and click the link.
  3. Confirm you're now a subscriber — in the dashboard's Subscribers list, or via curl -s $EMIT/v1/subscribers -H "Authorization: Bearer $EMIT_KEY". You should appear with status confirmed.
  4. Optionally, send yourself a test of the latest post: the dashboard's broadcast view has a Test send / preview button so you can eyeball the formatting before real posts go out.

If the confirmation email never arrives, re-check Step 2 — unverified or half-propagated DNS is the usual culprit. The feed's last poll status (visible per-feed in the dashboard) will also tell you if Emit is having trouble fetching your /index.xml.

Step 8Publish a post and watch it send

Now do the thing the whole setup exists for. Write a post and ship it:

hugo new posts/hello-newsletter.md
# write the post, then set draft = false
hugo                # rebuild
# deploy however you normally deploy

Once the new <item> is live in https://yourblog.com/index.xml, Emit's worker notices it on the next poll, renders the email, and sends it to every confirmed subscriber from posts@yourblog.com. Each send debits one credit from the balance you topped up in Step 3. Every email carries a one-click List-Unsubscribe header, so readers can leave cleanly and stay off your list.

That's the loop, and it's now permanent: write in Hugo → push → readers get an email. No dashboard visit required ever again.


Recap

From here you can add filters (skip certain tags, set a minimum word count), switch to a daily/weekly digest, or import an existing list. The API docs cover every endpoint, and there's an MCP server and a Claude Code skill if you'd rather drive Emit from an agent. Questions? team@rssemit.com.