SendGrid
Send through the Twilio SendGrid Mail Send API with the fullest field support of any adapter.
Capabilities
| Repeated headers | Idempotency | Scheduling | Personalized |
|---|---|---|---|
| No | none | Yes | native |
These values come from the adapter's exported capabilities declaration. The field support matrix covers normalized message fields.
The SendGrid adapter calls the Twilio SendGrid Mail Send API with plain fetch. It supports every EmailMessage field: recipients land in a personalizations entry, metadata becomes custom_args, and tags become categories.
Configure
Create an API key with Mail Send permission in the SendGrid dashboard and verify the sender identity or domain you use in from.
import { createEmailClient } from "@opencoredev/email-sdk";
import { sendgrid } from "@opencoredev/email-sdk/sendgrid";
export const email = createEmailClient({
adapters: [sendgrid({ apiKey: process.env.SENDGRID_API_KEY! })],
});Prop
Type
Send
Everything maps: cc, bcc, headers, attachments, tags, metadata, and even multiple replyTo addresses (sent as reply_to_list). sendAt schedules the send natively as send_at (a Unix timestamp in seconds); SendGrid accepts at most 72 hours in the future.
const result = await email.send({
from: "Acme <security@acme.com>",
to: [{ email: "user@example.com", name: "Ada" }],
subject: "Reset your password",
html: "<p>Click the link below to reset your password.</p>",
text: "Use the link below to reset your password.",
metadata: { userId: "user_123", flow: "password-reset" },
tags: [{ name: "type", value: "password-reset" }],
});
console.log(result.id); // SendGrid message id from the x-message-id headerSendGrid's send endpoint returns 202 Accepted with an empty body, so the adapter reads result.id from the x-message-id response header. metadata values arrive in SendGrid event webhooks as custom_args; tag values arrive as categories.
Personalized sends
SendGrid implements native sendPersonalized, so the adapter emits one personalizations entry per recipient and sends the complete set in one request.
await email.sendPersonalized({
message: {
from: "Acme <hello@acme.com>",
subject: "Hi %recipient.name%",
html: '<a href="https://acme.com/unsub?id=%recipient.id%">Unsubscribe</a>',
},
recipients: [
{ to: "ada@example.com", variables: { name: "Ada", id: "u_1" } },
{ to: "linus@example.com", variables: { name: "Linus", id: "u_2" } },
],
});Verify from the CLI
SENDGRID_API_KEY="SG...." npx email-sdk doctor --adapter sendgridSENDGRID_API_KEY="SG...." npx email-sdk send \
--adapter sendgrid \
--from "Acme <hello@acme.com>" \
--to user@example.com \
--subject "SendGrid smoke test" \
--text "It works" \
--dry-runDrop --dry-run for one real send. SendGrid account review and sender verification can block delivery even when the payload validates, and only a live send proves they are done.
Compare SendGrid with other providers
Side-by-side message-field support from the SDK capability matrix, with working code for both adapters:
