Appearance
Webhook Setup Guide
Fresh 🌱A comprehensive guide to setting up webhooks with ngrok and AgentMail, including account creation, inbox setup, and code examples.
This guide walks you through the complete process of setting up webhooks to receive real-time notifications from AgentMail. You'll learn how to create an ngrok account, set up an inbox, configure webhooks, and write a simple webhook receiver.
Prerequisites
Before you start, make sure you have:
- Python 3.8 or higher installed
- An AgentMail API Key
pippackage manager- Basic familiarity with Python and terminal commands
Webhooks require a public URL and tools like ngrok. If you'd rather skip that setup, WebSockets give you real-time events over a persistent connection with no external tooling. See the WebSockets quickstart.
Installation
First, install the required Python packages:
bash
pip install agentmail flask ngrokStep 1: Setting up account on Ngrok
Ngrok creates a secure tunnel from a public URL to your local development server, allowing AgentMail to send webhooks to your machine during development.
1.1 Create an ngrok account
Visit ngrok.com and click "Sign up" to create a free account.
1.2 Choose your platform and install
After logging in, ngrok will guide you through the setup process. Select your operating system and follow the installation instructions.
For macOS, you can install ngrok via Homebrew:
bash
brew install ngrokAfter installation, authenticate ngrok with your authtoken (found in your ngrok dashboard):
bash
ngrok config add-authtoken YOUR_AUTHTOKENStep 2: Creating the inbox on AgentMail
Before you can receive webhooks, you need an inbox to receive messages. Create one using the AgentMail API:
python
from agentmail import AgentMail
client = AgentMail()
# Create an inbox for your webhook agent
inbox = client.inboxes.create(
username="webhook-demo",
client_id="webhook-demo-inbox" # Ensures idempotency
)
print(f"Inbox created: {inbox.inbox_id}")The client_id parameter ensures that running this code multiple times won't create duplicate inboxes. If the inbox already exists, it will return the existing one.
Step 3: Configuring webhook on AgentMail
3.1 Start ngrok tunnel
In your terminal, start an ngrok tunnel to expose your local server (we'll use port 3000):
bash
ngrok http 3000You should see output similar to this:
Copy the Forwarding URL (e.g., https://your-subdomain.ngrok-free.app). This is the public URL that AgentMail will use to send webhooks.
When viewing your webhook receiver in the browser, use the http://127.0.0.1:3000 URL shown in the "Web Interface" line, not the ngrok URL. The ngrok URL is only for external services like AgentMail.
Why localhost? Safari is stricter than Chrome/Firefox when viewing development servers through HTTPS ngrok tunnels. Safari blocks local WebSockets and some dev-only scripts, which causes the page to show a loading spinner indefinitely due to Hot Module Replacement (HMR). This is expected development behavior and not a documentation issue. Using localhost or static builds avoids this problem.
3.2 Register webhook with AgentMail
Now register your webhook endpoint with AgentMail:
python
# Using the ngrok URL you copied
webhook_url = "https://your-subdomain.ngrok-free.app/webhooks"
webhook = client.webhooks.create(
url=webhook_url,
event_types=["message.received"], # add others (e.g. message.sent) as needed
client_id="webhook-demo-webhook" # Ensures idempotency
)
print(f"Webhook created: {webhook.webhook_id}")Step 4: Code example for receiving webhooks
Create a file named webhook_receiver.py with the following code:
python
from flask import Flask, request, Response
app = Flask(__name__)
@app.route('/')
def home():
"""Status page to verify server is running"""
return """
<html>
<body style="font-family: sans-serif; max-width: 800px; margin: 50px auto; padding: 20px;">
<h1>AgentMail Webhook Receiver</h1>
<div style="background: #4CAF50; color: white; padding: 10px 20px;
border-radius: 4px; display: inline-block; margin: 20px 0;">
Server is running
</div>
<div style="background: #e3f2fd; padding: 15px; border-radius: 4px;
border-left: 4px solid #2196F3;">
<h3>Webhook Endpoint Ready</h3>
<p>Your webhook endpoint is listening at: <code>POST /webhooks</code></p>
</div>
<h3>How to use:</h3>
<ul>
<li>Start ngrok: <code>ngrok http 3000</code></li>
<li>Register your webhook with AgentMail using the ngrok URL</li>
<li>Send a test email to your AgentMail inbox</li>
<li>Watch the console for incoming webhook events</li>
</ul>
</body>
</html>
"""
@app.route('/webhooks', methods=['POST'])
def receive_webhook():
"""Receives webhook events from AgentMail"""
payload = request.json
event_type = payload.get('event_type')
message = payload.get('message', {})
print(f"\nWebhook received: {event_type}")
print(f"From: {message.get('from_')}")
print(f"Subject: {message.get('subject')}\n")
return Response(status=200)
if __name__ == '__main__':
print("Starting webhook receiver on http://127.0.0.1:3000")
app.run(port=3000)Running your webhook receiver
- Make sure ngrok is running in one terminal window
- In another terminal, run your webhook receiver:
bash
python webhook_receiver.py- Send a test email to your AgentMail inbox
- Watch the console output for incoming webhook events
Viewing the result
Open your browser and visit http://127.0.0.1:3000 to see the status page confirming your webhook receiver is running:
Testing Your Setup
To test your webhook setup:
- Send an email to your inbox address (e.g.,
webhook-demo@agentmail.to) - Check your webhook receiver's console output
- You should see the webhook event details printed immediately
Now that your webhook is working, you can extend the receive_webhook() function to:
- Automatically reply to messages
- Process attachments
- Route emails to different handlers based on content
- Integrate with your AI agent workflows
Check out the Event-Driven Agent Example for a more advanced implementation.
Troubleshooting
- Verify ngrok is running and the forwarding URL matches your webhook registration
- Check that your Flask app is running on the correct port (3000)
- Ensure your webhook URL ends with
/webhooks - Look for errors in both the Flask console and ngrok web interface
Free ngrok accounts have 2-hour session limits. The tunnel will disconnect and you'll need to restart ngrok and update your webhook URL with AgentMail.
If port 3000 is already in use, choose a different port:
- Change the port in
app.run(port=XXXX) - Update the ngrok command:
ngrok http XXXX
Copy for Cursor / Claude
Copy one of the blocks below into Cursor or Claude for webhook setup in one shot.
python
"""
AgentMail Webhook Setup — copy into Cursor/Claude.
Flow: 1) ngrok http 3000 → copy URL. 2) inboxes.create(client_id=...) 3) webhooks.create(url=ngrok+/webhooks, event_types=[...], client_id=...)
4) Flask POST /webhooks: return 200 immediately, process request.json in background.
Local: use http://127.0.0.1:3000 in browser, not ngrok URL.
"""
from flask import Flask, request, Response
from agentmail import AgentMail
app = Flask(__name__)
client = AgentMail()
inbox = client.inboxes.create(username="webhook-demo", client_id="webhook-demo-inbox")
wh = client.webhooks.create(url="https://YOUR_NGROK.ngrok-free.app/webhooks", event_types=["message.received"], client_id="webhook-demo-webhook")
@app.route("/webhooks", methods=["POST"])
def receive():
payload = request.json
print(payload.get("event_type"), payload.get("message", {}).get("subject"))
return Response(status=200)typescript
/**
* AgentMail Webhook Setup — copy into Cursor/Claude.
*
* Flow: 1) ngrok http 3000. 2) inboxes.create({ clientId }). 3) webhooks.create({ url, eventTypes, clientId })
* 4) Express POST /webhooks: res.status(200).send(), process req.body async.
* Use express.raw() if you add Svix verification later.
*/
import express from "express";
import { AgentMailClient } from "agentmail";
const client = new AgentMailClient({ apiKey: process.env.AGENTMAIL_API_KEY! });
const inbox = await client.inboxes.create({ username: "webhook-demo", clientId: "webhook-demo-inbox" });
await client.webhooks.create({
url: "https://YOUR_NGROK.ngrok-free.app/webhooks",
eventTypes: ["message.received"],
clientId: "webhook-demo-webhook",
});
const app = express();
app.post("/webhooks", express.json(), (req, res) => {
console.log(req.body.event_type, req.body.message?.subject);
res.status(200).send();
});
app.listen(3000);Production Considerations
For production deployments:
- Use a dedicated server: Deploy your webhook receiver to a cloud service (AWS, GCP, Heroku, etc.) instead of using ngrok
- Implement webhook verification: Validate incoming requests are authentically from AgentMail
- Add error handling: Implement retry logic and error reporting
- Use async processing: Return 200 immediately and process webhooks in background jobs
- Hydrate messages via the API: Webhook payloads are capped at 1 MB. When this limit is exceeded, the
textandhtmlbody fields are omitted from the payload. Use the Get Message endpoint to fetch the complete message content, including the full body and attachments. See Payload size limit for details - Monitor webhook health: Set up logging and alerting for failed webhook deliveries
Learn how to verify webhook signatures for secure endpoints.
Learn more about how webhooks work and their payload structure.
Explore the full list of webhook event types.