{"id":"sendgrid","kind":"sdk","name":"SendGrid","slug":"sendgrid","description":"Twilio SendGrid email delivery SDKs.","vendor":"Twilio","languages":["python","nodejs","javascript","java","csharp","php","ruby","go"],"categories":["email"],"homepage":"https://sendgrid.com","docsUrl":"https://docs.sendgrid.com","githubUrl":"https://github.com/sendgrid","packages":[{"registry":"npm","name":"@sendgrid/mail","url":"https://www.npmjs.com/package/@sendgrid/mail"},{"registry":"pypi","name":"sendgrid","url":"https://pypi.org/project/sendgrid/"}],"tags":["email"],"skills":[{"name":"twilio-sendgrid-email-send","url":"https://skills.sh/twilio/ai/twilio-sendgrid-email-send","install":"npx skills add twilio/ai --skill twilio-sendgrid-email-send","sdk":"sendgrid","key":"sendgrid/twilio-sendgrid-email-send","description":"Send transactional and bulk email via the SendGrid v3 Mail Send API. Covers single sends, personalized batch sends with dynamic templates, scheduled sends with cancellation, attachments, and sandbox mode for testing. Use this skill when the caller has a SendGrid API key (SG.-prefix). Do NOT use this skill if the caller is using the Twilio Email API (comms.twilio.com) — that is a separate product with different credentials.","hasContent":true,"content":"---\nname: twilio-sendgrid-email-send\ndescription: >\n  Send transactional and bulk email via the SendGrid v3 Mail Send API.\n  Covers single sends, personalized batch sends with dynamic templates,\n  scheduled sends with cancellation, attachments, and sandbox mode for\n  testing. Use this skill when the caller has a SendGrid API key (SG.-prefix).\n  Do NOT use this skill if the caller is using the Twilio Email API\n  (comms.twilio.com) — that is a separate product with different credentials.\n---\n\n## Overview\n\n> **Agent safety:** Always confirm recipients, subject, and content with the user before sending. Email is irreversible once delivered. Never send email autonomously without explicit user approval — especially for batch sends to multiple recipients.\n\nAll email sending goes through `POST /v3/mail/send`. This endpoint returns `202 Accepted` (queued) — NOT `200 OK` (delivered). Delivery confirmation comes asynchronously via Event Webhook. See `twilio-sendgrid-webhooks`.\n\n---\n\n## Basic Send\n\n**Python**\n```python\nimport os, sendgrid\nfrom sendgrid.helpers.mail import Mail\n\nsg = sendgrid.SendGridAPIClient(os.environ[\"SENDGRID_API_KEY\"])\nmessage = Mail(\n    from_email=\"verified@yourdomain.com\",\n    to_emails=\"recipient@example.com\",\n    subject=\"Order Confirmation\",\n    html_content=\"<p>Your order #1234 is confirmed.</p>\"\n)\nresponse = sg.send(message)\nprint(f\"Status: {response.status_code}\")  # 202 = queued\n```\n\n**Node.js**\n```javascript\nconst sgMail = require(\"@sendgrid/mail\");\nsgMail.setApiKey(process.env.SENDGRID_API_KEY);\n\nconst [response] = await sgMail.send({\n    to: \"recipient@example.com\",\n    from: \"verified@yourdomain.com\",\n    subject: \"Order Confirmation\",\n    html: \"<p>Your order #1234 is confirmed.</p>\",\n});\nconsole.log(`Status: ${response.statusCode}`); // 202 = queued\n```\n\n---\n\n## Personalized Batch Send with Dynamic Templates\n\nDynamic templates use Handlebars syntax. Template IDs start with `d-`. Create templates in SendGrid Console > Email API > Dynamic Templates.\n\n**Python**\n```python\nfrom sendgrid.helpers.mail import Mail, To\n\nmessage = Mail(\n    from_email=\"noreply@yourdomain.com\",\n    to_emails=[\n        To(\"alice@example.com\", dynamic_template_data={\"name\": \"Alice\", \"order_id\": \"123\"}),\n        To(\"bob@example.com\", dynamic_template_data={\"name\": \"Bob\", \"order_id\": \"456\"}),\n    ],\n)\nmessage.template_id = \"d-xxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\nsg.send(message)\n```\n\n**Node.js**\n```javascript\nawait sgMail.send({\n    from: { email: \"noreply@yourdomain.com\" },\n    template_id: \"d-xxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\n    personalizations: [\n        { to: [{ email: \"alice@example.com\" }], dynamic_template_data: { name: \"Alice\", order_id: \"123\" } },\n        { to: [{ email: \"bob@example.com\" }], dynamic_template_data: { name: \"Bob\", order_id: \"456\" } },\n    ],\n});\n```\n\n**Recipients in the same `to` array within a single personalization can see each other.** For private sends, use separate personalizations (one per recipient).\n\n---\n\n## Scheduled Sends\n\nSchedule up to 72 hours in advance. Cancellation requires a batch ID assigned *before* sending.\n\n**Python**\n```python\nimport time, requests\n\nheaders = {\"Authorization\": f\"Bearer {os.environ['SENDGRID_API_KEY']}\", \"Content-Type\": \"application/json\"}\n\n# Get batch ID first\nbatch = requests.post(\"https://api.sendgrid.com/v3/mail/batch\", headers=headers).json()\n\n# Include batch_id and send_at in the message\nsend_at = int(time.time()) + 3600  # Unix SECONDS, not ms\n\n# Cancel if needed (before send_at)\nrequests.post(\"https://api.sendgrid.com/v3/user/scheduled_sends\",\n    headers=headers,\n    json={\"batch_id\": batch[\"batch_id\"], \"status\": \"cancel\"})\n```\n\n---\n\n## Attachments\n\nBase64-encode files in the `attachments` array. Total limit: 30MB per request (~22MB before encoding overhead).\n\n```python\nimport base64\nfrom sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileType, Disposition\n\nwith open(\"invoice.pdf\", \"rb\") as f:\n    encoded = base64.b64encode(f.read()).decode()\n\nmessage = Mail(from_email=\"billing@yourdomain.com\", to_emails=\"customer@example.com\",\n               subject=\"Your Invoice\", html_content=\"<p>Invoice attached.</p>\")\nmessage.attachment = Attachment(FileContent(encoded), FileName(\"invoice.pdf\"),\n                                FileType(\"application/pdf\"), Disposition(\"attachment\"))\nsg.send(message)\n```\n\n---\n\n## Categories and Custom Args\n\n**Categories** tag sends for analytics segmentation (up to 10 per message):\n```python\nmessage.category = [\"transactional\", \"order-confirmation\"]\n```\n\n**Custom Args** pass metadata through to Event Webhooks (key-value strings only):\n```python\nmessage.custom_args = {\"order_id\": \"1234\", \"env\": \"production\"}\n```\n\nThese appear in webhook event payloads, enabling you to correlate delivery events back to your application data.\n\n---\n\n## Sandbox Mode (Testing)\n\nValidates the request without delivering. Returns `200 OK` (not `202`).\n\n```python\nmessage.mail_settings = {\"sandbox_mode\": {\"enable\": True}}\nresponse = sg.send(message)  # 200 = validated, not sent\n```\n\n---\n\n## CANNOT\n\n- **Cannot send more than 1,000 recipients per API call** — Hard limit. Split into multiple requests.\n- **Cannot schedule sends more than 72 hours in advance** — `send_at` rejects timestamps beyond 72h.\n- **Cannot cancel a send after processing** — Only scheduled messages with a pre-assigned batch ID can be cancelled.\n- **Cannot use `send_at` with milliseconds** — JS `Date.now()` returns ms. Divide by 1000 or the timestamp is silently rejected (>72h).\n- **The `subject` field in personalizations is a plain string override** — To use dynamic subjects, set Handlebars variables (e.g., `{{{subject}}}`) in the Dynamic Template's subject field and pass values via `dynamic_template_data`. The personalizations `subject` key bypasses the template subject entirely.\n- **Undefined template variables render as empty strings** — No error for typos in `dynamic_template_data` keys. Silent failures.\n- **`413 Payload Too Large` returns nginx HTML, not JSON** — Exceeding 30MB returns HTML error page. Check Content-Type before parsing.\n- **Empty `content` when using `template_id`** — Omit the `content` field. If you include both, `template_id` takes precedence and `content` is ignored.\n\n> **Agent usage:** When sending email on behalf of a user, always report back what was sent — recipients, subject, and the API response status code. Maintain an application-level audit log for all sends.\n\n---\n\n## Next Steps\n\n- **Account setup and domain auth:** `twilio-sendgrid-account-setup`\n- **Templates and settings:** `twilio-sendgrid-email-settings`\n- **Delivery tracking via webhooks:** `twilio-sendgrid-webhooks`\n- **Manage bounces and unsubscribes:** `twilio-sendgrid-suppressions`\n","contentSource":"skills.sh/api/download/twilio/ai/twilio-sendgrid-email-send","contentFetchedAt":"2026-07-27T08:59:26.904Z"},{"name":"twilio-sendgrid-webhooks","url":"https://skills.sh/twilio/ai/twilio-sendgrid-webhooks","install":"npx skills add twilio/ai --skill twilio-sendgrid-webhooks","sdk":"sendgrid","key":"sendgrid/twilio-sendgrid-webhooks","description":"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).","hasContent":true,"content":"---\nname: twilio-sendgrid-webhooks\ndescription: >\n  Track email delivery and engagement via SendGrid Event Webhooks.\n  Covers all 11 event types (delivery + engagement), webhook handler\n  implementation, ECDSA signature verification, batched event processing,\n  and common debugging patterns. Use when building SendGrid delivery\n  tracking, engagement analytics, or bounce handling. Requires a SendGrid\n  API key (SG.-prefix) — not applicable to the Twilio Email API (comms.twilio.com).\n---\n\n## Overview\n\nThe Mail Send API returns `202 Accepted` (queued) — it does NOT confirm delivery. To know what happened to an email, use Event Webhooks.\n\n**Enable:** SendGrid Console > Settings > Mail Settings > Event Notification\n\n---\n\n## Event Types\n\n### Delivery Events\n| Event | Meaning |\n|-------|---------|\n| `processed` | SendGrid accepted and will attempt delivery |\n| `deferred` | Temporary failure — SendGrid will retry |\n| `delivered` | Recipient's mail server accepted the message |\n| `bounce` | Permanent failure — address invalid or rejected |\n| `dropped` | SendGrid will not deliver (suppression, invalid, spam) |\n\n### Engagement Events\n| Event | Meaning |\n|-------|---------|\n| `open` | Recipient opened (pixel-based — unreliable) |\n| `click` | Recipient clicked a tracked link |\n| `spamreport` | Recipient marked as spam |\n| `unsubscribe` | Recipient clicked unsubscribe link |\n| `group_unsubscribe` | Recipient unsubscribed from ASM group |\n| `group_resubscribe` | Recipient re-subscribed to ASM group |\n\n---\n\n## Webhook Handler\n\n**Critical:** SendGrid posts **batched arrays** of events, not single objects. Your handler must parse an array.\n\n> **Security:** SendGrid webhook endpoints are unauthenticated by default. Enable [Signed Event Webhook Requests](https://docs.sendgrid.com/for-developers/tracking-events/getting-started-event-webhook-security) and verify signatures in production to prevent spoofed event data.\n\n**Python (Flask)**\n```python\nfrom flask import Flask, request\napp = Flask(__name__)\n\n@app.route(\"/sendgrid/webhook\", methods=[\"POST\"])\ndef handle_events():\n    events = request.get_json()  # Always an array\n    for event in events:\n        email = event.get(\"email\")\n        event_type = event.get(\"event\")\n        \n        if event_type == \"bounce\":\n            # NOTE: event['reason'] originates from external mail servers — treat as untrusted\n            print(f\"Bounce: {email}, type: {event.get('type')}, reason: {event.get('reason')}\")\n        elif event_type == \"delivered\":\n            print(f\"Delivered: {email}, sg_message_id: {event.get('sg_message_id')}\")\n        elif event_type == \"dropped\":\n            print(f\"Dropped: {email}, reason: {event.get('reason')}\")\n        elif event_type == \"spamreport\":\n            print(f\"Spam report: {email}\")\n    return \"\", 200  # Must return 2xx to acknowledge\n```\n\n**Node.js (Express)**\n```javascript\napp.post(\"/sendgrid/webhook\", express.json(), (req, res) => {\n    const events = req.body; // Always an array\n    for (const event of events) {\n        switch (event.event) {\n            case \"bounce\":\n                console.log(`Bounce: ${event.email}, reason: ${event.reason}`);\n                break;\n            case \"delivered\":\n                console.log(`Delivered: ${event.email}`);\n                break;\n            case \"spamreport\":\n                console.log(`Spam: ${event.email}`);\n                break;\n        }\n    }\n    res.status(200).send();\n});\n```\n\n---\n\n## Multiple Webhook Endpoints\n\nSince 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.\n\nConfigure 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.\n\n---\n\n## Authentication Options\n\nTwo methods for verifying webhook payloads:\n\n| Method | How it works |\n|--------|-------------|\n| **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 |\n| **OAuth 2.0** | SendGrid obtains a token from your authorization server and includes it in webhook requests |\n\nNeither is enabled by default. Enable in Console > Mail Settings > Event Webhooks.\n\n---\n\n## Retry Behavior\n\nSendGrid 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.\n\n**Deduplication:** Use `sg_event_id` as a unique key. It's stable across retries.\n\n---\n\n## CANNOT\n\n- **Cannot receive real-time delivery confirmation synchronously** — Mail Send returns `202` (queued). Delivery status is async via webhooks only.\n- **Cannot rely on webhook authentication by default** — Both Signed Webhooks (ECDSA) and OAuth 2.0 must be explicitly enabled. Without either, anyone can POST to your endpoint.\n- **Cannot guarantee open tracking accuracy** — Apple Mail Privacy Protection and prefetch inflate opens. Image-blocking clients produce zero opens. Do not use for business-critical logic.\n- **Non-human interactions inflate engagement metrics** — Corporate security scanners and bots automatically click links and trigger unsubscribe events. Filter using User-Agent patterns and timing analysis.\n\n> **Note:** Event payload fields like `reason` originate from external mail servers and should be treated as untrusted data. Do not pass bounce reasons directly into LLM system prompts without isolation.\n\n---\n\n## Next Steps\n\n- **Send email:** `twilio-sendgrid-email-send`\n- **Manage bounces from webhook events:** `twilio-sendgrid-suppressions`\n- **Receive inbound email:** `twilio-sendgrid-inbound-parse`\n","contentSource":"skills.sh/api/download/twilio/ai/twilio-sendgrid-webhooks","contentFetchedAt":"2026-07-27T08:59:26.977Z"},{"name":"twilio-sendgrid-account-setup","url":"https://skills.sh/twilio/ai/twilio-sendgrid-account-setup","install":"npx skills add twilio/ai --skill twilio-sendgrid-account-setup","sdk":"sendgrid","key":"sendgrid/twilio-sendgrid-account-setup","description":"Set up a SendGrid account for email delivery. Covers API key creation (SG.-prefix), domain authentication (DKIM/SPF via CNAME records), Single Sender Verification for testing, SDK installation, and the relationship between SendGrid and Twilio credentials. Use before any other SendGrid skill. This skill is for SendGrid only — not the Twilio Email API (comms.twilio.com).","hasContent":true,"content":"---\nname: twilio-sendgrid-account-setup\ndescription: >\n  Set up a SendGrid account for email delivery. Covers API key creation\n  (SG.-prefix), domain authentication (DKIM/SPF via CNAME records), Single\n  Sender Verification for testing, SDK installation, and the relationship\n  between SendGrid and Twilio credentials. Use before any other SendGrid skill.\n  This skill is for SendGrid only — not the Twilio Email API (comms.twilio.com).\n---\n\n## Overview\n\nSendGrid is Twilio's email delivery engine but uses a **completely separate authentication system** — SendGrid API keys (starting with `SG.`) are not Twilio API keys. You cannot use Account SID/Auth Token for SendGrid, and no Twilio MCP tools wrap SendGrid.\n\n---\n\n## Quickstart\n\n1. Get your API key from [SendGrid Console > Settings > API Keys](https://app.sendgrid.com/settings/api_keys)\n2. Set environment variable:\n\n```bash\nexport SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n```\n\n3. Install SDK:\n\n| Language | Install | Package |\n|----------|---------|---------|\n| Python | `pip install sendgrid` | `sendgrid` |\n| Node.js | `npm install @sendgrid/mail` | `@sendgrid/mail` (v8.x) |\n| Java | Maven | `com.sendgrid:sendgrid-java` |\n| C# | `dotnet add package SendGrid` | `SendGrid` |\n| Ruby | `gem install sendgrid-ruby` | `sendgrid-ruby` |\n| PHP | `composer require sendgrid/sendgrid` | `sendgrid/sendgrid` |\n| Go | `go get github.com/sendgrid/sendgrid-go` | `sendgrid-go` |\n\n4. Authenticate your sending domain (see below)\n\n---\n\n## API Key Scopes\n\n| Scope | Use for | Risk |\n|-------|---------|------|\n| **Full Access** | Development only | Can do everything — never deploy with this |\n| **Restricted Access** | Production | Scope to only what your app needs (e.g., Mail Send only) |\n| **Billing Access** | Account management | Separate from mail operations |\n\nA \"Mail Send\" restricted key can send email but cannot read suppressions, manage templates, or access stats. If you get `403 Forbidden`, check key permissions.\n\n---\n\n## SMTP Relay (Alternative to API)\n\nSendGrid also supports SMTP for sending. Useful for frameworks with built-in SMTP support (e.g., Laravel, Django, Rails).\n\n| Setting | Value |\n|---------|-------|\n| **Server** | `smtp.sendgrid.net` |\n| **Port** | `587` (TLS) or `465` (SSL) |\n| **Username** | `apikey` (literal string, not your key name) |\n| **Password** | Your SendGrid API key (`SG.xxx`) |\n\n---\n\n## Domain Authentication (Required for Production)\n\nSingle Sender Verification is for testing only. Production requires domain authentication for deliverability.\n\n**Setup:** SendGrid Console > Settings > Sender Authentication > Authenticate Your Domain\n\nCreate 3 CNAME DNS records:\n1. `s1._domainkey.yourdomain.com` → `s1.domainkey.u1234.wl.sendgrid.net` (DKIM)\n2. `s2._domainkey.yourdomain.com` → `s2.domainkey.u1234.wl.sendgrid.net` (DKIM)\n3. `em1234.yourdomain.com` → `u1234.wl.sendgrid.net` (return path)\n\nVerify via API: `GET /v3/whitelabel/domains/{id}/validate`\n\n**DMARC:** After setting up DKIM and SPF via domain authentication, configure a DMARC DNS record (`_dmarc.yourdomain.com`) to instruct receiving servers how to handle authentication failures. Start with `p=none` for monitoring before enforcing.\n\n**Dedicated IP (Pro+ plans):** Isolates your sending reputation. Requires an IP warming schedule — start with low volume and increase over 30 days.\n\n---\n\n## SendGrid and Twilio\n\n| Twilio product | How it uses SendGrid | Sends email? |\n|----------------|---------------------|-------------|\n| **SendGrid** (this skill) | Direct email delivery via `api.sendgrid.com` | Yes |\n| **Twilio Email API** | Direct email delivery via `comms.twilio.com/v1/emails` — uses Twilio creds, not SendGrid keys | Yes (separate product) |\n| **Verify** | OTP via `channel: 'email'` | Delegates to SendGrid via Mailer config |\n| **Conversations** | Tracks EMAIL as a channel type | No — logs/tracks only |\n| **Flex** | Email channel for agents | Uses SendGrid for delivery |\n\n**Servers:**\n- Global: `https://api.sendgrid.com`\n- EU regional: `https://api.eu.sendgrid.com`\n\n---\n\n## CANNOT\n\n- **Cannot use Twilio credentials for SendGrid** — Separate API keys (`SG.`-prefix), separate Console, separate billing.\n- **Cannot access SendGrid via Twilio MCP tools** — No MCP integration. Use SDK or direct REST.\n- **Single Sender Verification requires re-verification on address change** — Changing the sender email requires a new verification. Use Domain Authentication for production.\n- **Domain Authentication requires DNS access** — 3 CNAME records needed. If you can't modify DNS, you can't authenticate.\n- **Domain Authentication API returns stale entries** — `GET /v3/whitelabel/domains` includes old invalid entries. Filter by `valid: true`.\n- **API Key Secret shown only at creation** — Cannot retrieve afterward. Store immediately.\n\n> **Security:** Your API key is shown only once at creation. Never display, log, or repeat a user's API key in responses. If a user shares their key in conversation, advise them to rotate it immediately. Store keys in a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.), not in code or environment files committed to version control.\n\n---\n\n## Next Steps\n\n- **Send email:** `twilio-sendgrid-email-send`\n- **Domain settings and templates:** `twilio-sendgrid-email-settings`\n- **Delivery tracking:** `twilio-sendgrid-webhooks`\n- **Docs:** [SendGrid API Reference](https://www.twilio.com/docs/sendgrid/api-reference)\n","contentSource":"skills.sh/api/download/twilio/ai/twilio-sendgrid-account-setup","contentFetchedAt":"2026-07-27T08:59:27.228Z"}],"official":true,"generatedAt":"2026-07-27T09:02:29.956Z"}