Emit watches your RSS feed and emails new posts to your subscribers from your own domain. You pay for emails actually sent — no subscriber tiers, no monthly minimum, no free-tier theater.
We poll 14,000 RSS feeds every five minutes. Here's the ETag trick that drops 92% of those requests to a 304 — and why it matters for your bill.
Read on blog.dev →Emit does the four boring things between "you published a post" and "someone opened the email," and nothing else.
10,000 readers you email twice a month shouldn't cost the same as a daily blast. You're billed per email sent — full stop.
Send from a verified domain over Resend or Amazon SES with DKIM, bounce & complaint handling, list-unsubscribe headers, and one-click opt-out baked in.
A typed OpenAPI 3.1 spec, bearer keys, idempotent writes, and signed webhooks for key events. No dashboard you're forced through — wire it in and forget it.
# returns an sk_live_… key, shown once curl -X POST api.rssemit.com/v1/accounts \ -d '{"name":"My Blog", "email":"me@blog.dev"}'
# polled every 5m · conditional GET curl -X POST api.rssemit.com/v1/feeds \ -H "Authorization: Bearer $KEY" \ -d '{"url":"…/rss.xml", "from":"posts@blog.dev"}'
# $20 ≈ 25,000 emails. autopilot. curl -X POST api.rssemit.com/v1/billing \ -H "Authorization: Bearer $KEY" \ -d '{"amount_usd":20}' # → new posts mail themselves
We ship a typed OpenAPI spec, a one-shot MCP server, and a Claude Code skill that handle domain verification, DNS records, subscriber import, and the first broadcast — in a single conversation.
No SDKs to chase across versions. Bearer auth, JSON in, JSON out.
# Send a one-off broadcast to all confirmed subscribers curl -X POST https://api.rssemit.com/v1/broadcasts \ -H "Authorization: Bearer $EMIT_KEY" \ -H "Content-Type: application/json" \ -d '{ "subject": "Shipping Emit v1.4", "from": "posts@blog.dev", "html": "<h1>We shipped.</h1>", "audience": "all_confirmed" }' # → { "id": "bcst_8Hk…", "status": "queued", "recipients": 4218 }
// node 20 · zero deps const r = await fetch("https://api.rssemit.com/v1/broadcasts", { method: "POST", headers: { "Authorization": `Bearer ${process.env.EMIT_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ subject: "Shipping Emit v1.4", from: "posts@blog.dev", html: "<h1>We shipped.</h1>", audience: "all_confirmed", }), }); const { id, recipients } = await r.json(); console.log(`queued ${recipients} to ${id}`);
# python 3.11 · stdlib only import os, json, urllib.request as u req = u.Request( "https://api.rssemit.com/v1/broadcasts", method="POST", headers={ "Authorization": f"Bearer {os.environ['EMIT_KEY']}", "Content-Type": "application/json", }, data=json.dumps({ "subject": "Shipping Emit v1.4", "from": "posts@blog.dev", "html": "<h1>We shipped.</h1>", "audience": "all_confirmed", }).encode(), ) res = json.loads(u.urlopen(req).read()) print(f"queued {res['recipients']} to {res['id']}")
// go 1.22 body, _ := json.Marshal(map[string]any{ "subject": "Shipping Emit v1.4", "from": "posts@blog.dev", "html": "<h1>We shipped.</h1>", "audience": "all_confirmed", }) req, _ := http.NewRequest("POST", "https://api.rssemit.com/v1/broadcasts", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer " + os.Getenv("EMIT_KEY")) req.Header.Set("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close()
# ruby 3.3 require "net/http"; require "json" uri = URI("https://api.rssemit.com/v1/broadcasts") req = Net::HTTP::Post.new(uri, { "Authorization" => "Bearer #{ENV['EMIT_KEY']}", "Content-Type" => "application/json", }) req.body = { subject: "Shipping Emit v1.4", from: "posts@blog.dev", html: "<h1>We shipped.</h1>", audience: "all_confirmed", }.to_json res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) } puts JSON.parse(res.body)["id"]
Prepaid credits. $10 minimum top-up. One credit, one email — including bounces, but only on confirmed delivery attempts. That's the whole pricing page.
Default template is plaintext-first, HTML-friendly, dark-mode safe, and respects your
post's typography. Override it per feed with your own raw HTML via the
feed's template_html.
<!-- emit interpolates {{ }} from the RSS <item> — title, link, html, pubDate, and any <custom:*> tags --> <mj-section> <mj-column> <mj-text font-size="11" color="#6b6b70"> {{ feed.title }} · issue #{{ issue }} </mj-text> <mj-text font-size="30" font-weight="600"> {{ title }} </mj-text> <mj-raw>{{ html_excerpt }}</mj-raw> <mj-button href="{{ link }}"> Continue reading → </mj-button> </mj-column> </mj-section>
$0.0008 per email actually
delivered. If you publish twice in January and ten times in February, your bill
reflects exactly that — no minimum, no rollover games. Credits don't expire.
List-Unsubscribe + List-Unsubscribe-Post) is
on every send. Suppressed addresses are queryable via
GET /v1/subscribers?status=bounced (or complained) and
exportable through GET /v1/subscribers/export.
If-None-Match +
If-Modified-Since). You can switch to on_publish mode and
POST to /v1/feeds/{id}/ping from a webhook for instant delivery.
add a newsletter to my blog using emit in Claude Code or Codex can do
DNS records, domain verification, subscriber import, and the first broadcast without
you leaving the terminal.
from: posts@yourdomain.dev. No
<@rssemit.com>
ever appears in your subscribers' inboxes.
us-east-1. We never sell subscriber data — there's
nothing in it for us, and it would tank the only thing we're selling.
The dashboard exists. We just hope you never need it.