
how inbound email webhook processing works for AI agents
Learn how inbound email webhook processing lets AI agents receive, parse, and act on emails in real time. A practical guide to building event-driven agent workflows.
Your agent needs to respond to an email from a customer. The email arrived six seconds ago. If you're polling an IMAP inbox every 30 seconds, your agent might not even know about it yet. If you're polling every five minutes, the customer has already moved on.
Polling is the default for most email integrations, and it's fine for humans who check their inbox a few times an hour. For an AI agent that needs to act on incoming messages in real time, it's the wrong model entirely. Inbound email webhook processing flips the flow: instead of your agent asking "anything new?" over and over, the email infrastructure pushes a structured payload to your agent the instant a message lands.
This is how you build agents that actually react to email, not agents that periodically glance at it.
How inbound email webhook processing works#
The mechanics are simpler than most people expect. Here's what happens when someone sends an email to your agent's address:
- The email arrives at your domain and the MX record routes it to your email infrastructure provider.
- The provider receives the raw MIME message (headers, body, attachments, all of it).
- The provider parses that raw message into a structured JSON payload with clean fields: sender, subject, body text, HTML, attachments as URLs.
- The provider sends an HTTP POST request to the webhook URL you registered.
- Your handler validates the request signature to confirm it's authentic.
- Your agent's logic runs with full email context: who sent it, what they said, any files they attached.
- Your handler returns a 200 response to acknowledge receipt.
That's it. No IMAP connections to manage, no credentials to rotate, no polling intervals to tune. The email shows up, your agent gets a structured notification, and it acts.
Why agents need webhooks instead of polling#
The practical difference between polling and webhooks goes beyond latency. With IMAP polling, your agent maintains a persistent connection (or reconnects constantly), tracks which messages it has already seen, handles connection timeouts, and parses raw MIME data itself. That's a lot of code for something that isn't your agent's actual job.
With webhooks, your agent exposes a single HTTP endpoint. The email arrives pre-parsed, pre-structured, and ready to process. Your agent doesn't need to know anything about email protocols. It receives JSON, does its work, and moves on.
For multi-agent systems, this matters even more. A triage agent can receive the webhook, classify the email, and fan it out to specialized sub-agents: one handles support requests, another processes invoices, a third logs everything for audit trails. Each agent gets exactly the data it needs without any of them touching an IMAP library.
Setting up an inbound email webhook#
The setup has three parts: getting an inbox, registering a webhook URL, and writing a handler.
With LobsterMail, the first two steps collapse into almost nothing. Your agent provisions its own inbox and registers a webhook in a few lines:
import { LobsterMail } from "lobstermail";
const lm = new LobsterMail();
// Agent creates its own inbox
const inbox = await lm.createInbox({ name: "support-agent" });
// Register a webhook for inbound emails
const webhook = await lm.createWebhook({
url: "https://your-agent.example.com/hooks/email",
events: ["email.received"],
});
No human had to sign up for anything. No DNS records to configure. The agent did it itself.
For the handler side, here's what a basic Next.js webhook endpoint looks like:
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
export async function POST(request: NextRequest) {
const body = await request.text();
const signature = request.headers.get("x-lobstermail-signature");
// Verify the webhook signature
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET!)
.update(body)
.digest("hex");
if (signature !== expected) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
const payload = JSON.parse(body);
const { from, subject, preview, threadId } = payload.data;
// Your agent logic here
console.log(`Email from ${from}: ${subject}`);
return NextResponse.json({ received: true }, { status: 200 });
}
The signature verification step is not optional. Without it, anyone who discovers your webhook URL can send fake payloads and trick your agent into acting on fabricated emails. Always validate the HMAC signature against the secret you received when you created the webhook.
Keeping conversations coherent with thread context#
One thing most webhook guides skip: how does your agent maintain context across a multi-turn email conversation?
Every inbound email payload includes a threadId. When a customer replies to your agent's response, that reply carries the same thread ID as the original message. Your agent can use this to pull the full conversation history before composing its next reply.
// Fetch the full thread for context
const thread = await lm.getThread(payload.data.threadId);
// thread.messages contains every email in the conversation
const conversationHistory = thread.messages.map((msg) => ({
role: msg.direction === "inbound" ? "user" : "assistant",
content: msg.textBody,
}));
// Pass the history to your LLM for a context-aware response
const reply = await generateAgentResponse(conversationHistory);
Without thread tracking, your agent treats every inbound email as a brand-new conversation. That's how you get agents that ask "How can I help you?" to someone who's been going back and forth for three days.
Handling failures and retries#
Your webhook endpoint will go down. Deployments happen, servers restart, cloud providers hiccup. The question is whether you lose emails when it does.
Good email infrastructure retries failed webhook deliveries with exponential backoff. LobsterMail retries for up to 24 hours with increasing delays between attempts. If your endpoint returns a 5xx error or times out, the payload gets queued and retried automatically.
On your side, the key principle is idempotency. Your handler should produce the same result whether it processes a given email once or three times. The simplest approach: store the emailId from each payload and skip processing if you've seen it before.
const { emailId } = payload.data;
// Check if we already processed this email
if (await hasBeenProcessed(emailId)) {
return NextResponse.json({ received: true }, { status: 200 });
}
// Process the email
await processEmail(payload.data);
await markAsProcessed(emailId);
Return a 200 status code quickly, even if your agent's actual processing takes a while. Accept the webhook, queue the work internally, and respond. If your handler takes 30 seconds to reply while your LLM generates a response, the webhook delivery system might assume it failed and retry, creating duplicate processing.
Security considerations for agent webhook endpoints#
Exposing a public HTTP endpoint for your agent creates a real attack surface. Beyond signature verification, here are the practical defenses:
Replay prevention. Include a timestamp check. If the webhook payload's timestamp is more than five minutes old, reject it. This prevents an attacker from capturing a valid signed payload and replaying it later.
IP allowlisting. If your infrastructure provider publishes their webhook source IPs, restrict your endpoint to only accept requests from those addresses. This is a simple network-level filter that blocks most noise.
Injection scanning. Inbound emails can contain prompt injection attempts, especially if your agent feeds email content directly into an LLM. LobsterMail scans every inbound email for injection patterns and includes a risk score in the webhook payload. If injectionRiskScore is above your threshold, quarantine the email instead of processing it blindly.
That last point is unique to agent email infrastructure. Traditional email services don't scan for prompt injection because they were built before LLMs existed. If your agent processes email content with AI, this isn't a theoretical risk. People will try it.
Testing webhooks locally#
You don't need to deploy to production to test your webhook handler. Tools like ngrok or cloudflared create a tunnel from a public URL to your local development server:
ngrok http 3000
Use the generated URL as your webhook endpoint during development. Send a test email to your agent's inbox and watch the payload arrive in your local terminal. Once everything works, swap the URL to your production endpoint.
When webhooks aren't enough#
Webhooks work well for real-time, event-driven processing. But some agent workflows need to batch-process emails, search across historical messages, or access emails that arrived before the webhook was registered. For those cases, you'll still want the API for fetching and searching. Webhooks handle the "something just happened" signal. The API handles "let me look at what happened before."
Most production agent deployments use both: webhooks for real-time triggers, API calls for context and history.
If you want your agent to have its own inbox with webhook support out of the box, . Your agent provisions the inbox and registers its webhook. No human signup required.
Frequently asked questions
What is an inbound email webhook and how is it different from IMAP polling?
An inbound email webhook is an HTTP POST request sent to your server whenever an email arrives, delivering the message as structured JSON in real time. IMAP polling requires your application to repeatedly connect and check for new messages, adding latency and connection overhead.
How does an email get converted into an HTTP POST webhook payload?
Your email provider receives the raw MIME message, parses the headers, body (text and HTML), and attachments into structured fields, then POSTs a JSON payload to the webhook URL you registered. You never touch the raw MIME data.
What fields are included in a typical inbound email webhook payload?
Most payloads include from, to, subject, textBody, htmlBody, attachments (as download URLs), threadId, emailId, receivedAt, and security metadata. The exact shape varies by provider.
How do I verify that an inbound webhook request is authentic?
Compute an HMAC-SHA256 hash of the raw request body using the signing secret you received when creating the webhook. Compare it to the signature header sent with the request. If they don't match, reject the request with a 401.
What happens if my webhook endpoint is down when an email arrives?
Most providers queue failed deliveries and retry with exponential backoff. LobsterMail retries for up to 24 hours. You won't lose emails from brief outages, but extended downtime could exhaust the retry window.
How do I build an idempotent webhook handler to prevent duplicate processing?
Store the emailId from each processed payload. Before processing a new webhook, check if that ID already exists in your store. If it does, return a 200 without processing again. This handles retries safely.
Can an AI agent have its own persistent email address tied to a webhook?
Yes. With LobsterMail, your agent provisions its own inbox programmatically and registers a webhook URL. The inbox persists across sessions, giving the agent a stable email identity. No human signup needed.
How do I route different inbound email addresses to different agent workflows?
Create separate inboxes for each workflow and register distinct webhook URLs for each one. Alternatively, use a single webhook endpoint and route internally based on the inboxId or to address in the payload.
How do I handle email attachments in a webhook payload?
Attachments typically arrive as download URLs or base64-encoded data in the payload. Download them from the provided URLs within your handler and pass them to your agent's processing logic. Avoid storing large attachments in memory.
How do I maintain conversation thread context across multiple webhook calls?
Use the threadId included in each webhook payload to fetch the full conversation history via the API. Pass the ordered messages to your LLM as conversation context so it can compose replies that reflect the full discussion.
Can I use inbound email webhooks with n8n or Zapier?
Yes. Most webhook-based email providers give you a payload URL that works as a trigger in n8n, Zapier, or Make. Point the workflow trigger to your webhook URL and map the JSON fields to your automation steps.
How do I test inbound email webhooks locally before deploying?
Use a tunneling tool like ngrok or cloudflared to expose your local server to the internet. Register the tunnel URL as your webhook endpoint, send a test email, and inspect the payload in your local environment.
Is it possible to send a reply email directly from a webhook handler?
Yes. After processing the inbound payload, call your email provider's send API from within the same handler to fire off a reply. Just make sure you return the 200 acknowledgment quickly and handle the reply asynchronously if it involves LLM processing.
What are the security risks of exposing a public webhook endpoint for inbound email?
The main risks are payload forgery (someone POSTing fake emails), replay attacks (resending captured valid payloads), and prompt injection (malicious email content designed to manipulate your AI agent). Signature verification, timestamp checks, and injection scoring mitigate these.


