Send email from DaloyJS with SendGrid
Twilio SendGrid is a long-standing email delivery service that combines transactional and marketing sending. This guide uses the official @sendgrid/mail SDK, which wraps the v3 Mail Send REST API.
1. Provision
- Create a SendGrid account, enable 2FA, then go to Settings → API Keys and generate a Restricted Access key with only Mail Send → Full Access enabled.
- Complete Domain Authentication (SPF/DKIM CNAMEs) for your sending domain, or use Single Sender Verification for quick tests only.
2. Install
pnpm add @sendgrid/mail3. Environment variables
# .env
SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxxxx
SENDGRID_FROM="Acme <no-reply@acme.example.com>"4. Plugin
// src/plugins/sendgrid.ts
import sgMail from "@sendgrid/mail";
import type { App } from "@daloyjs/core";
sgMail.setApiKey(process.env.SENDGRID_API_KEY!);
export const sendgridPlugin = {
name: "sendgrid",
register(app: App) {
app.decorate("email", {
async send({ to, subject, text, html }) {
const [res] = await sgMail.send({
from: process.env.SENDGRID_FROM!,
to,
subject,
text,
html,
});
// SendGrid returns 202 with an X-Message-Id header on success
const id = res.headers["x-message-id"] ?? "";
return { id: String(id) };
},
});
},
};
declare module "@daloyjs/core" {
interface AppState {
email: {
send(msg: {
to: string;
subject: string;
text?: string;
html?: string;
}): Promise<{ id: string }>;
};
}
}5. Use it in a route
import { z } from "zod";
import { App, secureHeaders, rateLimit } from "@daloyjs/core";
import { sendgridPlugin } from "./plugins/sendgrid";
const app = new App();
app.use(secureHeaders());
app.use(rateLimit({ windowMs: 60_000, max: 10 }));
app.register(sendgridPlugin);
app.route({
method: "POST",
path: "/contact",
operationId: "submitContact",
request: {
body: z.object({
to: z.string().email(),
subject: z.string().min(1).max(200),
message: z.string().min(1).max(5000),
}),
},
responses: {
202: { description: "Queued", body: z.object({ id: z.string() }) },
},
handler: async ({ body, state }) => {
const { id } = await state.email.send({
to: body.to,
subject: body.subject,
text: body.message,
});
return { status: 202, body: { id } };
},
});Dynamic templates
For server-side templating, create a Dynamic Transactional Template in the SendGrid UI and pass its ID with substitution values:
await sgMail.send({
from: process.env.SENDGRID_FROM!,
to,
templateId: "d-1234567890abcdef1234567890abcdef",
dynamicTemplateData: {
firstName: "Devlin",
cartUrl: "https://acme.example.com/cart/abc",
},
});Error handling
On non-2xx responses the SDK throws an error with response.body.errors describing each failure. Surface those to your client through the standard problem+json helper rather than echoing raw text.
Runtimes
The @sendgrid/mail package is Node-oriented (it uses @sendgrid/client with Node's HTTPS module). For Cloudflare Workers or Vercel Edge, call the v3 REST API directly with fetch against https://api.sendgrid.com/v3/mail/send using the same JSON body and a Bearer token.
See also Resend, Postmark, and the email integrations overview.