{"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"}