How to Integrate SendGrid with Astro: Complete Guide
Step-by-step guide to integrating SendGrid with your Astro website. Setup, configuration, and best practices.
How to Integrate SendGrid with Astro: Complete Guide
SendGrid, now part of Twilio, is one of the most widely used email delivery platforms, handling transactional emails, marketing campaigns, and email validation for businesses of all sizes. It offers a robust API, dynamic email templates, and detailed delivery analytics. Integrating SendGrid with Astro lets you send emails from contact forms, user notifications, and automated workflows directly from your server-rendered Astro application.
This guide walks through setting up SendGrid in an Astro project, sending emails via the API, using dynamic templates, and handling form submissions.
Prerequisites
Before starting, make sure you have:
- Node.js 18+ installed
- An existing Astro project with SSR enabled (v3.0+)
- A SendGrid account (free tier at sendgrid.com, 100 emails/day)
- A SendGrid API key with Mail Send permissions
- A verified sender identity (single sender or domain authentication)
Installation
Install the official SendGrid Node.js library:
npm install @sendgrid/mail
Add an SSR adapter if you have not already:
npx astro add node
Configuration
Environment Variables
Add your SendGrid API key and sender email to .env:
SENDGRID_API_KEY=SG.your_api_key_here
SENDGRID_FROM_EMAIL=noreply@yourdomain.com
SENDGRID_FROM_NAME=Your Site Name
Setting Up the SendGrid Client
Create a utility file that initializes the SendGrid client:
// src/lib/sendgrid.ts
import sgMail from '@sendgrid/mail';
sgMail.setApiKey(import.meta.env.SENDGRID_API_KEY);
export { sgMail };
export const defaultFrom = {
email: import.meta.env.SENDGRID_FROM_EMAIL,
name: import.meta.env.SENDGRID_FROM_NAME,
};
Astro Config
Enable server or hybrid rendering:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'hybrid',
adapter: node({ mode: 'standalone' }),
});
Common Patterns
Sending a Basic Email
Create an API endpoint for sending emails:
// src/pages/api/send-email.ts
import type { APIRoute } from 'astro';
import { sgMail, defaultFrom } from '../../lib/sendgrid';
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
try {
await sgMail.send({
to: body.to,
from: defaultFrom,
subject: body.subject,
text: body.message,
html: `<p>${body.message}</p>`,
});
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
const message = error.response?.body?.errors?.[0]?.message || 'Failed to send email';
return new Response(JSON.stringify({ error: message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
};
Contact Form Implementation
Build a server-rendered contact page:
---
// src/pages/contact.astro
export const prerender = false;
let submitted = false;
let errorMsg = '';
if (Astro.request.method === 'POST') {
const formData = await Astro.request.formData();
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const message = formData.get('message') as string;
const { sgMail, defaultFrom } = await import('../lib/sendgrid');
try {
await sgMail.send({
to: 'you@yourdomain.com',
from: defaultFrom,
replyTo: email,
subject: `Contact: ${name}`,
html: `
<h2>New Contact Submission</h2>
<p><strong>From:</strong> ${name} (${email})</p>
<hr />
<p>${message.replace(/\n/g, '<br />')}</p>
`,
});
submitted = true;
} catch {
errorMsg = 'Failed to send your message. Please try again later.';
}
}
---
<html>
<body>
<h1>Contact</h1>
{submitted ? (
<p>Thank you! Your message has been sent.</p>
) : (
<form method="POST">
{errorMsg && <p style="color: red;">{errorMsg}</p>}
<label>Name <input type="text" name="name" required /></label>
<label>Email <input type="email" name="email" required /></label>
<label>Message <textarea name="message" rows="6" required></textarea></label>
<button type="submit">Send</button>
</form>
)}
</body>
</html>
Using Dynamic Templates
SendGrid's Dynamic Templates let you design emails in the SendGrid dashboard and pass data variables from your code:
// src/pages/api/welcome.ts
import type { APIRoute } from 'astro';
import { sgMail, defaultFrom } from '../../lib/sendgrid';
export const POST: APIRoute = async ({ request }) => {
const { email, name } = await request.json();
try {
await sgMail.send({
to: email,
from: defaultFrom,
templateId: 'd-your_template_id_here',
dynamicTemplateData: {
name: name,
login_url: 'https://yourdomain.com/login',
current_year: new Date().getFullYear(),
},
});
return new Response(JSON.stringify({ success: true }), { status: 200 });
} catch (error: any) {
return new Response(JSON.stringify({ error: 'Send failed' }), { status: 500 });
}
};
Create the template in SendGrid Dashboard > Email API > Dynamic Templates. Use Handlebars syntax like {{name}} in the template design to reference the variables you pass.
Sending to Multiple Recipients
await sgMail.send({
to: ['user1@example.com', 'user2@example.com'],
from: defaultFrom,
subject: 'Update Notification',
html: '<p>This is an update.</p>',
isMultiple: true, // Sends individual emails, not one with all addresses visible
});
Adding Attachments
import { readFileSync } from 'node:fs';
await sgMail.send({
to: 'user@example.com',
from: defaultFrom,
subject: 'Your Report',
html: '<p>Your report is attached.</p>',
attachments: [
{
content: readFileSync('/path/to/report.pdf').toString('base64'),
filename: 'report.pdf',
type: 'application/pdf',
disposition: 'attachment',
},
],
});
Troubleshooting
"Forbidden" or 403 error: Your API key may not have Mail Send permissions. Go to SendGrid Dashboard > Settings > API Keys, and make sure your key has at least "Mail Send" access. Full Access keys also work.
Emails not arriving: Check your SendGrid Activity Feed (Email API > Activity) for bounce, block, or drop events. Common causes are unverified sender identity, recipient domain blocking, or content flagged as spam.
"Sender identity not verified" error: You must verify either a single sender email or authenticate your entire domain before sending. Go to Settings > Sender Authentication in the SendGrid dashboard.
HTML email rendering issues: Different email clients render HTML differently. Use inline CSS styles instead of <style> blocks. SendGrid's Dynamic Templates include a visual editor that handles cross-client compatibility.
Rate limiting on free tier: The free tier allows 100 emails per day. If you need more volume, upgrade to the Essentials plan. Implement queuing on your server side if you need to send bursts.
Module import error in production: Some deployment platforms require the SendGrid package to be listed in dependencies, not devDependencies. Check your package.json and move it if needed.
Conclusion
SendGrid provides a battle-tested email delivery infrastructure that integrates cleanly with Astro's API routes and server-side rendering. The combination of the Node.js SDK for programmatic sending and Dynamic Templates for visual email design covers most email use cases. Start with the free tier for development and contact forms, verify your sending domain early, and use the Activity Feed to monitor deliverability as you scale.
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.