Receive Email in Real Time Without Running a Webhook Server
Webhooks are the usual way to receive email in real time — but they need a public URL you control, which a CI runner, a laptop behind NAT, or a quick script doesn’t have. Here’s how to receive inbound email as it arrives over Server-Sent Events instead: one HTTP connection, no server to host.
The webhook problem
To use a webhook you must expose an HTTPS endpoint the service can POST to. Fine for a deployed backend — painful when:
- You’re in CI — the runner is ephemeral and not publicly reachable.
- You’re developing locally — you’d need ngrok or a tunnel.
- You’re writing a quick script or agent — standing up a web server is overkill.
The SSE alternative
Server-Sent Events flips it around: instead of the service calling you, you open one long-lived GET request and it streams events down as they happen. Plain HTTP, works through any firewall that allows outbound HTTPS, no public URL. With Devmailr, open the stream for a mailbox:
curl -N "https://api.devmailr.app/api/mailboxes/$ID/stream" \
-H "Authorization: Bearer $DEVMAILR_API_KEY"Each inbound email is pushed as an event:
event: email.received
data: {"_id":"...","from":"noreply@stripe.com","subject":"Your code","extractedCodes":["482913"]}Consuming it in Node
const res = await fetch(`${BASE}/mailboxes/${inbox._id}/stream`, {
headers: { Authorization: `Bearer ${process.env.DEVMAILR_API_KEY}` },
})
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
let idx
while ((idx = buffer.indexOf('\n\n')) !== -1) {
const frame = buffer.slice(0, idx)
buffer = buffer.slice(idx + 2)
const dataLine = frame.split('\n').find((l) => l.startsWith('data:'))
if (!dataLine) continue // skip ':' heartbeat comments
const email = JSON.parse(dataLine.slice(5).trim())
console.log('Got email from', email.from, '— code', email.extractedCodes?.[0])
}
}Filtering
Narrow the stream with the same filters as the list and wait endpoints:
# only emails from your app that carry a code
curl -N "$BASE/mailboxes/$ID/stream?from=noreply@myapp.com&has_code=true" -H "$AUTH"When to use which
- SSE — CI, local dev, scripts, agents: anything that can’t host a URL. One connection, no infra.
- Webhooks — deployed backends that want push delivery to a stable endpoint, with retries.
- /wait — when you want exactly one email and then to move on (most tests).
Keep-alive: The stream sends a : heartbeat comment every ~25s to hold the connection open — ignore any frame without a data: line, and reconnect if it drops.
Related reading
Try it in two minutes
Create a disposable prefix@devmailr.app inbox over the API, send mail to it from your app, and read it in your tests. Free plan, no card required.