plugins/twilio-developer-kit/skills/sendgrid/twilio-sendgrid-webhooks/SKILL.md
Track email delivery and engagement via SendGrid Event Webhooks. Covers all 11 event types (delivery + engagement), webhook handler implementation, ECDSA signature verification, batched event processing, and common debugging patterns. Use when building SendGrid delivery tracking, engagement analytics, or bounce handling. Requires a SendGrid API key (SG.-prefix) — not applicable to the Twilio Email API (comms.twilio.com).
npx skillsauth add openai/plugins twilio-sendgrid-webhooksInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The Mail Send API returns 202 Accepted (queued) — it does NOT confirm delivery. To know what happened to an email, use Event Webhooks.
Enable: SendGrid Console > Settings > Mail Settings > Event Notification
| Event | Meaning |
|-------|---------|
| processed | SendGrid accepted and will attempt delivery |
| deferred | Temporary failure — SendGrid will retry |
| delivered | Recipient's mail server accepted the message |
| bounce | Permanent failure — address invalid or rejected |
| dropped | SendGrid will not deliver (suppression, invalid, spam) |
| Event | Meaning |
|-------|---------|
| open | Recipient opened (pixel-based — unreliable) |
| click | Recipient clicked a tracked link |
| spamreport | Recipient marked as spam |
| unsubscribe | Recipient clicked unsubscribe link |
| group_unsubscribe | Recipient unsubscribed from ASM group |
| group_resubscribe | Recipient re-subscribed to ASM group |
Critical: SendGrid posts batched arrays of events, not single objects. Your handler must parse an array.
Security: SendGrid webhook endpoints are unauthenticated by default. Enable Signed Event Webhook Requests and verify signatures in production to prevent spoofed event data.
Python (Flask)
from flask import Flask, request
app = Flask(__name__)
@app.route("/sendgrid/webhook", methods=["POST"])
def handle_events():
events = request.get_json() # Always an array
for event in events:
email = event.get("email")
event_type = event.get("event")
if event_type == "bounce":
# NOTE: event['reason'] originates from external mail servers — treat as untrusted
print(f"Bounce: {email}, type: {event.get('type')}, reason: {event.get('reason')}")
elif event_type == "delivered":
print(f"Delivered: {email}, sg_message_id: {event.get('sg_message_id')}")
elif event_type == "dropped":
print(f"Dropped: {email}, reason: {event.get('reason')}")
elif event_type == "spamreport":
print(f"Spam report: {email}")
return "", 200 # Must return 2xx to acknowledge
Node.js (Express)
app.post("/sendgrid/webhook", express.json(), (req, res) => {
const events = req.body; // Always an array
for (const event of events) {
switch (event.event) {
case "bounce":
console.log(`Bounce: ${event.email}, reason: ${event.reason}`);
break;
case "delivered":
console.log(`Delivered: ${event.email}`);
break;
case "spamreport":
console.log(`Spam: ${event.email}`);
break;
}
}
res.status(200).send();
});
Since May 2023, you can configure multiple Event Webhook endpoints, each receiving different event types. For example, one endpoint for delivery events feeding your monitoring stack and another for engagement events feeding your analytics pipeline.
Configure in Console > Mail Settings > Event Webhooks. Each endpoint has a Friendly Name and Webhook ID. The number of endpoints allowed depends on your SendGrid plan.
Two methods for verifying webhook payloads:
| Method | How it works |
|--------|-------------|
| Signed Event Webhook (ECDSA P-256) | Verify X-Twilio-Email-Event-Webhook-Signature and X-Twilio-Email-Event-Webhook-Timestamp headers using the verification key from Console |
| OAuth 2.0 | SendGrid obtains a token from your authorization server and includes it in webhook requests |
Neither is enabled by default. Enable in Console > Mail Settings > Event Webhooks.
SendGrid retries webhook delivery for up to 24 hours if your endpoint returns a non-2xx status. Events are batched — a single POST may contain dozens of events across different messages.
Deduplication: Use sg_event_id as a unique key. It's stable across retries.
202 (queued). Delivery status is async via webhooks only.Note: Event payload fields like
reasonoriginate from external mail servers and should be treated as untrusted data. Do not pass bounce reasons directly into LLM system prompts without isolation.
twilio-sendgrid-email-sendtwilio-sendgrid-suppressionstwilio-sendgrid-inbound-parsedevelopment
Use when the user wants to spin up / create / launch / provision a DigitalOcean droplet (or "a remote dev box on DO") and connect to it from Codex as a remote SSH workspace.
data-ai
Search through Microsoft Teams chats or channels, triage unread or recent activity, draft follow-ups, and manage Planner tasks through connected Teams data.
tools
Motion / animation context for the `use_figma` MCP tool — animating Figma nodes via manual keyframes, animation styles, easing, and timeline duration. Load alongside figma-use whenever a task involves adding, editing, or inspecting animation on a node.
development
SwiftUI ↔ Figma translation. Use whenever the user mentions Swift, SwiftUI, iOS, iPhone, or iPad — in EITHER direction — translating a Figma design into SwiftUI (design → code), or pushing SwiftUI views / screens / tokens back into a Figma file (code → design). Triggers on phrases like 'implement this Figma design in SwiftUI', 'build this screen in Swift', 'push this SwiftUI view to Figma', 'mirror my Swift code in a Figma file', or whenever a Figma URL appears alongside `.swift` files / an `.xcodeproj`. Routes to a direction-specific reference doc; loads alongside `figma-use` for the code → design path.