Emit pipes anything on the web into a clean RSS feed — and any RSS feed into an email newsletter, a Slack channel, a webhook. Describe an interest in plain English and get a filtered feed; merge and summarize the feeds you already read; deliver them anywhere. Every stage is real RSS — pay per use, no tiers, no lock-in by construction.
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 →Everything below is a real pipeline you can build in the next five minutes — one API call or a couple of clicks in the dashboard. Each one ends in an RSS feed URL, an email, a Slack message, or a webhook. Your pick.
The classic. Point emit at your RSS feed and every new post emails your subscribers from your own domain — per-post or as a daily/weekly digest. $1.60 per 1,000 emails, nothing per subscriber.
Their /changelog has no RSS feed? emit makes one from
the page itself, then pushes every new entry into the channel where your team will
actually see it. Page → feed + a Slack
broadcast.
Type "vintage synth restorations and new Eurorack releases" and a feed finds the sources, reads everything they publish, and emits an RSS feed of only the items that match — each with a score and a reason.
Feed emit the 30 subscriptions you can't keep up with. Get back one merged RSS feed — duplicates collapsed, everything off-topic filtered out by a plain-English prompt, each item optionally pre-summarized.
A city permits page, a government notices board, a jobs page, a band's tour dates. emit diffs the page and emits new entries as RSS — read them, or get them as an email alert the day they appear.
Every new matching item can also arrive as a signed, retried webhook — straight into your CRM, n8n, Zapier, or that cron job you've been meaning to write. Delivery log included, HMAC verified.
Every stage's output is a real, subscribable RSS feed (Atom 1.0 + JSON Feed 1.1) — so you can tap in or out anywhere, with any reader or tool that has spoken RSS for the last twenty years. Enter with feeds you already have, a page with no feed, or just a sentence describing what you want to follow. Exit to email, Slack, Discord, webhooks — or just read the feed.
Paste feeds you already read. Point at a page with no feed and we diff it into one. Or describe an interest and the feed discovers the sources for you.
An LLM relevance filter tuned by a plain-language prompt, cross-source dedupe, and per-item summaries. Every filtered-out item stays auditable — you can always see why you didn't see something.
The newsletter engine you came for, plus Slack and Discord sinks, signed webhooks with retries and a delivery log — and always the feed itself. Never gated: your data exits as easily as it entered.
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 emit_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_email":"posts@blog.dev"}'
# $20 ≈ 12,500 emails. autopilot. curl -X POST api.rssemit.com/v1/billing/topup \ -H "Authorization: Bearer $KEY" \ -d '{"amount_usd":20}' # one-time: verify your email + sending # domain (DNS), then 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", "body_html": "<h1>We shipped.</h1>", "feed_id": "8Hk2q9f1-…" }' # → { "id": "8Hk2q9f1…", "status": "queued", "recipient_count": 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", body_html: "<h1>We shipped.</h1>", feed_id: "8Hk2q9f1-…", }), }); const { id, recipient_count } = await r.json(); console.log(`queued ${recipient_count} 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", "body_html": "<h1>We shipped.</h1>", "feed_id": "8Hk2q9f1-…", }).encode(), ) res = json.loads(u.urlopen(req).read()) print(f"queued {res['recipient_count']} to {res['id']}")
// go 1.22 body, _ := json.Marshal(map[string]any{ "subject": "Shipping Emit v1.4", "body_html": "<h1>We shipped.</h1>", "feed_id": "8Hk2q9f1-…", }) 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", body_html: "<h1>We shipped.</h1>", feed_id: "8Hk2q9f1-…", }.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 meter for everything. Making a feed is free — filtered feeds, turning pages into RSS, polling sources, serving your Atom/JSON feeds, Slack/Discord pushes, and webhooks all cost nothing. You pay for exactly three kinds of work, when they happen:
Worked example: a feed filtering ~40 new items a day ≈ 240 credits ≈ $0.38 / month. Add per-item summaries and it's ≈ $2.30.
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.
<!-- raw HTML, rendered with Jinja. Set per feed via template_html. Variables: title, body, url, unsubscribe_url --> <h1>{{ title }}</h1> <div>{{ body }}</div> <a href="{{ url }}"> Continue reading → </a> <hr> <p> <a href="{{ unsubscribe_url }}"> Unsubscribe </a> </p>
If yours isn't here, will@heltonlabs.com — a human reads it.
$0.0016 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.