Files
chloeclaw/skills/agentmail/references/WEBHOOKS.md
2026-03-02 20:22:49 +00:00

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

  1. Create account at ngrok.com
  2. Install: brew install ngrok (macOS) or download from website
  3. 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