Newsletters from your blog. Slack pings when a page changes. Webhooks when a dependency ships. emit is the small, cheap pipe between any source and any inbox — every stage a real RSS feed, so you can tap in or out anywhere.
Keep writing on your own site. Every post becomes a clean email to your subscribers — around a thousand sends for $1.60.
$1.60 ≈ 1,000 emailsEvery pipe below is real and buildable in the next five minutes — one API call or a couple of clicks. Each one ends in an RSS feed URL, an email, a Slack message, or a JSON POST to your own code.
Every new post becomes a clean, readable email to your list, from your own domain. Around a thousand sends for $1.60.
Start this pipe →Watch any URL — pricing pages, competitors, notices. Your team hears about changes in Slack, even if the page never had a feed.
Start this pipe →New versions of your dependencies fire a webhook — open an issue, kick off CI, tell the channel. Clean JSON, no poller to write.
Start this pipe →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.
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 →# 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"]
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>
Pricing questions — credits, the free tier, bounces — are answered on the pricing page. If yours isn't either place, will@heltonlabs.com — a human reads it.
application/json with an X-Emit-Event: sink.item header;
the body is {"event","sink_id","name","item"}. Set the schedule to
daily or weekly and emit batches them into a single sink.digest POST
with an items array. No poller, no XML parsing, no cron — see
RSS to webhook.
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.