7.2 KiB
AgentMail Webhooks Guide
Webhooks enable real-time, event-driven email processing. When events occur (like receiving a message), AgentMail immediately sends a POST request to your registered endpoint.
Event Types
message.received
Triggered when a new email arrives. Contains full message and thread data.
Use case: Auto-reply to support emails, process attachments, route messages
{
"type": "event",
"event_type": "message.received",
"event_id": "evt_123abc",
"message": {
"inbox_id": "support@agentmail.to",
"thread_id": "thd_789ghi",
"message_id": "msg_123abc",
"from": [{"name": "Jane Doe", "email": "jane@example.com"}],
"to": [{"name": "Support", "email": "support@agentmail.to"}],
"subject": "Question about my account",
"text": "I need help with...",
"html": "<p>I need help with...</p>",
"timestamp": "2023-10-27T10:00:00Z",
"labels": ["received"]
},
"thread": {
"thread_id": "thd_789ghi",
"subject": "Question about my account",
"participants": ["jane@example.com", "support@agentmail.to"],
"message_count": 1
}
}
message.sent
Triggered when you successfully send a message.
{
"type": "event",
"event_type": "message.sent",
"event_id": "evt_456def",
"send": {
"inbox_id": "support@agentmail.to",
"thread_id": "thd_789ghi",
"message_id": "msg_456def",
"timestamp": "2023-10-27T10:05:00Z",
"recipients": ["jane@example.com"]
}
}
message.delivered
Triggered when your message reaches the recipient's mail server.
message.bounced
Triggered when a message fails to deliver.
{
"type": "event",
"event_type": "message.bounced",
"bounce": {
"type": "Permanent",
"sub_type": "General",
"recipients": [{"address": "invalid@example.com", "status": "bounced"}]
}
}
message.complained
Triggered when recipients mark your message as spam.
Local Development Setup
Step 1: Install Dependencies
pip install agentmail flask ngrok python-dotenv
Step 2: Set up ngrok
- Create account at ngrok.com
- Install:
brew install ngrok(macOS) or download from website - Authenticate:
ngrok config add-authtoken YOUR_AUTHTOKEN
Step 3: Create Webhook Receiver
Create webhook_receiver.py:
from flask import Flask, request, Response
import json
from agentmail import AgentMail
import os
app = Flask(__name__)
client = AgentMail(api_key=os.getenv("AGENTMAIL_API_KEY"))
@app.route('/webhook', methods=['POST'])
def handle_webhook():
payload = request.json
if payload['event_type'] == 'message.received':
message = payload['message']
# Auto-reply example
response_text = f"Thanks for your email about '{message['subject']}'. We'll get back to you soon!"
client.inboxes.messages.send(
inbox_id=message['inbox_id'],
to=message['from'][0]['email'],
subject=f"Re: {message['subject']}",
text=response_text
)
print(f"Auto-replied to {message['from'][0]['email']}")
return Response(status=200)
if __name__ == '__main__':
app.run(port=3000)
Step 4: Start Services
Terminal 1 - Start ngrok:
ngrok http 3000
Copy the forwarding URL (e.g., https://abc123.ngrok-free.app)
Terminal 2 - Start webhook receiver:
python webhook_receiver.py
Step 5: Register Webhook
from agentmail import AgentMail
client = AgentMail(api_key="your_api_key")
webhook = client.webhooks.create(
url="https://abc123.ngrok-free.app/webhook",
client_id="dev-webhook"
)
Step 6: Test
Send an email to your AgentMail inbox and watch the console output.
Production Deployment
Webhook Verification
Verify incoming webhooks are from AgentMail:
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-AgentMail-Signature')
if not verify_webhook(request.data.decode(), signature, webhook_secret):
return Response(status=401)
# Process webhook...
Error Handling
Return 200 status quickly, process in background:
from threading import Thread
import time
def process_webhook_async(payload):
try:
# Heavy processing here
time.sleep(5) # Simulate work
handle_message(payload)
except Exception as e:
print(f"Webhook processing error: {e}")
# Log to error tracking service
@app.route('/webhook', methods=['POST'])
def handle_webhook():
payload = request.json
# Return 200 immediately
Thread(target=process_webhook_async, args=(payload,)).start()
return Response(status=200)
Retry Logic
AgentMail retries failed webhooks with exponential backoff. Handle idempotency:
processed_events = set()
@app.route('/webhook', methods=['POST'])
def handle_webhook():
event_id = request.json['event_id']
if event_id in processed_events:
return Response(status=200) # Already processed
# Process event...
processed_events.add(event_id)
return Response(status=200)
Common Patterns
Auto-Reply Bot
def handle_message_received(message):
if 'support' in message['to'][0]['email']:
# Support auto-reply
reply_text = "Thanks for contacting support! We'll respond within 24 hours."
elif 'sales' in message['to'][0]['email']:
# Sales auto-reply
reply_text = "Thanks for your interest! A sales rep will contact you soon."
else:
return
client.inboxes.messages.send(
inbox_id=message['inbox_id'],
to=message['from'][0]['email'],
subject=f"Re: {message['subject']}",
text=reply_text
)
Message Routing
def route_message(message):
subject = message['subject'].lower()
if 'billing' in subject or 'payment' in subject:
forward_to_slack('#billing-team', message)
elif 'bug' in subject or 'error' in subject:
create_github_issue(message)
elif 'feature' in subject:
add_to_feature_requests(message)
Attachment Processing
def process_attachments(message):
for attachment in message.get('attachments', []):
if attachment['content_type'] == 'application/pdf':
# Process PDF
pdf_content = base64.b64decode(attachment['content'])
text = extract_pdf_text(pdf_content)
# Reply with extracted text
client.inboxes.messages.send(
inbox_id=message['inbox_id'],
to=message['from'][0]['email'],
subject=f"Re: {message['subject']} - PDF processed",
text=f"I extracted this text from your PDF:\n\n{text}"
)
Webhook Security
- Always verify signatures in production
- Use HTTPS endpoints only
- Validate payload structure before processing
- Implement rate limiting to prevent abuse
- Return 200 quickly to avoid retries