# How to Integrate Bolt.new with SendGrid

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 20 minutes
- Last updated: April 2026

## TL;DR

Send transactional emails from your Bolt.new app using SendGrid's Web API v3 and the @sendgrid/mail npm package — it is pure JavaScript and works in Bolt's WebContainer. Create a Next.js API route that calls SendGrid server-side, store your API key in environment variables only, and use dynamic templates for HTML emails. Incoming webhook events require a deployed URL — test on Netlify or Bolt Cloud.

## Send Transactional Emails from Bolt.new with SendGrid

Every production web app eventually needs to send emails — signup confirmations, password resets, order receipts, contact form notifications, and trial expiry reminders. SendGrid is among the most popular choices for this because it offers generous free tier limits (100 emails/day), extremely high deliverability, and a clean API that works with standard npm packages in any Node.js environment.

The @sendgrid/mail package is pure JavaScript with no native C/C++ dependencies — a critical property for Bolt.new's WebContainer, which cannot compile native Node.js modules. Installing it via npm in Bolt works instantly, and making outbound API calls to SendGrid's infrastructure works from the WebContainer preview. You can build and test email-sending features entirely in Bolt before deploying.

The security model requires careful attention. Your SendGrid API key must never appear in client-side React components or in NEXT_PUBLIC_ prefixed environment variables. It belongs exclusively in server-side API routes, where it is never bundled into the JavaScript that ships to the browser. A Next.js API route serves as the secure relay: your React frontend calls your own API route, which calls SendGrid with the key. This pattern protects the key and also prevents users from bypassing your email logic to send arbitrary emails through your SendGrid account.

## Before you start

- A SendGrid account at sendgrid.com — the free tier allows 100 emails/day with full API access
- A SendGrid API key created in Settings → API Keys with 'Mail Send' permissions
- A verified sender identity in SendGrid (Settings → Sender Authentication) — SendGrid blocks email until you verify your sending domain or email address
- A Bolt.new account with a new Next.js project open
- A Netlify account for deployment and webhook configuration

## Step-by-step guide

### 1. Create a SendGrid API key and verify your sender identity

SendGrid requires two setup steps before you can send any email: creating an API key and verifying your sender identity. Both are done in the SendGrid dashboard at app.sendgrid.com.

For the API key: go to Settings → API Keys → Create API Key. Name it clearly (e.g., 'My App Production'). Choose 'Restricted Access' and enable 'Mail Send' permissions — this is the minimum required scope. Avoid using Full Access keys unless you specifically need to manage templates, contacts, or domains via API. Copy the key immediately — SendGrid shows it only once. The key begins with `SG.` followed by a long string.

For sender identity: SendGrid will not deliver email unless your sending address or domain is verified. The fastest path is Single Sender Verification: go to Settings → Sender Authentication → Get Started (under Single Sender Verification), enter your name and email address, and click verify. SendGrid emails you a confirmation link — click it. For production with a custom domain (recommended for better deliverability and brand consistency), use Domain Authentication instead: Settings → Sender Authentication → Authenticate Your Domain. This involves adding DNS records to your domain and takes a few minutes to propagate.

Add your credentials to .env.local in your Bolt project. The API key must be a server-side only variable — do not add `NEXT_PUBLIC_` prefix.

```
// lib/sendgrid.ts
import sgMail from '@sendgrid/mail';

sgMail.setApiKey(process.env.SENDGRID_API_KEY!);

interface EmailOptions {
  to: string | string[];
  subject: string;
  text: string;
  html?: string;
  templateId?: string;
  dynamicTemplateData?: Record<string, unknown>;
  replyTo?: string;
}

export async function sendEmail(options: EmailOptions): Promise<void> {
  const fromEmail = process.env.SENDGRID_FROM_EMAIL;
  if (!fromEmail) throw new Error('SENDGRID_FROM_EMAIL is not set');

  const message: sgMail.MailDataRequired = {
    to: options.to,
    from: fromEmail,
    subject: options.subject,
    text: options.text,
    ...(options.html && { html: options.html }),
    ...(options.templateId && {
      templateId: options.templateId,
      dynamicTemplateData: options.dynamicTemplateData,
    }),
    ...(options.replyTo && { replyTo: options.replyTo }),
  };

  await sgMail.send(message);
}

export async function sendBulkEmail(
  messages: EmailOptions[]
): Promise<void> {
  const fromEmail = process.env.SENDGRID_FROM_EMAIL!;
  const prepared = messages.map(opts => ({
    to: opts.to,
    from: fromEmail,
    subject: opts.subject,
    text: opts.text,
    ...(opts.html && { html: opts.html }),
  })) as sgMail.MailDataRequired[];

  await sgMail.sendMultiple(prepared);
}
```

**Expected result:** A configured SendGrid helper library in lib/sendgrid.ts and environment variables set in .env.local, ready for use in API routes.

### 2. Build the contact form email API route

The contact form API route is the most common first use case — it handles POST requests from your frontend form, validates the input, sends an email notification to your inbox, and optionally sends a confirmation to the form submitter.

The key security principle: this route must be a server-side API route (`app/api/`), never a client-side function. If you call SendGrid directly from a React component, the API key is exposed in the browser network requests. The API route keeps the key server-side and also lets you implement rate limiting, spam filtering, and validation logic before sending.

Adding a honeypot field (a hidden form field that humans never fill but bots do) and basic rate limiting significantly reduces spam submissions from contact forms. The route should also validate that the email format is valid and that required fields are present before calling SendGrid.

For the confirmation email back to the user, use a simple but professional HTML template inline rather than a SendGrid template for simple contact confirmations — it avoids the need to manage template IDs in environment variables for a one-off email.

```
// app/api/contact/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { sendEmail } from '@/lib/sendgrid';

interface ContactFormBody {
  name: string;
  email: string;
  message: string;
  honeypot?: string;
}

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

export async function POST(request: NextRequest) {
  const body: ContactFormBody = await request.json();
  const { name, email, message, honeypot } = body;

  // Honeypot check — bots fill hidden fields
  if (honeypot) {
    return NextResponse.json({ success: true }); // Silent accept to not alert bots
  }

  // Validation
  if (!name?.trim() || name.trim().length < 2) {
    return NextResponse.json({ error: 'Name is required' }, { status: 400 });
  }
  if (!email || !emailRegex.test(email)) {
    return NextResponse.json({ error: 'Valid email is required' }, { status: 400 });
  }
  if (!message?.trim() || message.trim().length < 10) {
    return NextResponse.json({ error: 'Message must be at least 10 characters' }, { status: 400 });
  }

  const notificationHtml = `
    <h2>New Contact Form Submission</h2>
    <p><strong>Name:</strong> ${name}</p>
    <p><strong>Email:</strong> <a href="mailto:${email}">${email}</a></p>
    <p><strong>Message:</strong></p>
    <p>${message.replace(/\n/g, '<br>')}</p>
  `;

  const confirmationHtml = `
    <h2>Thanks for reaching out, ${name}!</h2>
    <p>We received your message and will get back to you within 1-2 business days.</p>
    <p><em>Your message:</em><br>${message.replace(/\n/g, '<br>')}</p>
  `;

  try {
    await sendEmail({
      to: process.env.CONTACT_EMAIL_TO!,
      subject: `New Contact Form Submission from ${name}`,
      text: `Name: ${name}\nEmail: ${email}\nMessage: ${message}`,
      html: notificationHtml,
      replyTo: email,
    });

    await sendEmail({
      to: email,
      subject: 'We received your message',
      text: `Thanks for reaching out, ${name}! We will get back to you within 1-2 business days.`,
      html: confirmationHtml,
    });

    return NextResponse.json({ success: true });
  } catch (error) {
    console.error('SendGrid error:', error);
    return NextResponse.json({ error: 'Failed to send message' }, { status: 500 });
  }
}
```

**Expected result:** A working contact form API that sends email notifications to your inbox and confirmation emails to submitters, with spam protection via honeypot.

### 3. Use SendGrid dynamic templates for branded HTML emails

For emails where design matters — welcome emails, order confirmations, password resets — use SendGrid's dynamic templates rather than inline HTML. Templates are created in the SendGrid dashboard, give you a visual editor, and support handlebars-style template variables (`{{firstName}}`, `{{orderTotal}}`) that your API fills in at send time.

Create a template in SendGrid: go to Email API → Dynamic Templates → Create a Dynamic Template. Name it, add a version, and use the visual editor to design your email. Use handlebars syntax for dynamic values: `{{name}}` for the recipient's name, `{{orderItems}}` for an array of items, `{{ctaUrl}}` for a link. Save and copy the template ID (starts with `d-`).

In your API route, pass the template ID and the dynamic data object. SendGrid fills in the template variables server-side before sending — your code never generates HTML for these emails, just passes the data. This separation is valuable for non-technical team members who want to edit email design without touching your codebase.

For transactional emails sent at volume (thousands per day), templates also help with compliance: you can add unsubscribe links and preference center links once in the template rather than managing them in every API call. The template handles the footer, legal text, and branding consistently.

```
// app/api/email/welcome/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { sendEmail } from '@/lib/sendgrid';

export async function POST(request: NextRequest) {
  const { email, name, plan = 'free' } = await request.json();

  if (!email || !name) {
    return NextResponse.json(
      { error: 'email and name are required' },
      { status: 400 }
    );
  }

  const templateId = process.env.SENDGRID_WELCOME_TEMPLATE_ID;
  if (!templateId) {
    // Fallback to plain text if no template configured
    await sendEmail({
      to: email,
      subject: `Welcome to the app, ${name}!`,
      text: `Hi ${name}, welcome! Get started at ${process.env.NEXT_PUBLIC_APP_URL}`,
      html: `<h1>Welcome, ${name}!</h1><p>Get started at <a href="${process.env.NEXT_PUBLIC_APP_URL}">the app</a>.</p>`,
    });
  } else {
    await sendEmail({
      to: email,
      subject: `Welcome to the app, ${name}!`,
      text: `Welcome ${name}! We are glad to have you.`,
      templateId,
      dynamicTemplateData: {
        name,
        plan,
        getting_started_url: `${process.env.NEXT_PUBLIC_APP_URL}/onboarding`,
        current_year: new Date().getFullYear(),
      },
    });
  }

  return NextResponse.json({ success: true });
}
```

**Expected result:** Welcome emails send successfully using SendGrid dynamic templates, with dynamic user data populated from the API route.

### 4. Deploy to Netlify and configure SendGrid event webhooks

Email sending works in Bolt.new's WebContainer during development — outbound API calls to SendGrid's mail endpoints are fully functional in the preview. When you are ready to configure delivery tracking (bounces, opens, clicks, unsubscribes), you need a deployed URL for SendGrid's Event Webhook, which sends POST requests to your endpoint when email events occur.

Deploy your app to Netlify by clicking Deploy in Bolt.new and connecting to Netlify via OAuth. After deployment, go to Netlify's dashboard → Site Configuration → Environment Variables and add: `SENDGRID_API_KEY`, `SENDGRID_FROM_EMAIL`, `SENDGRID_WELCOME_TEMPLATE_ID`, `CONTACT_EMAIL_TO`, and `NEXT_PUBLIC_APP_URL`. Trigger a redeploy for the variables to apply.

For Event Webhooks: create an API route at `/api/sendgrid/webhook` that handles POST requests. In the SendGrid dashboard, go to Settings → Mail Settings → Event Webhook, enter your Netlify URL (`https://your-app.netlify.app/api/sendgrid/webhook`), and select the events you want to track. Enable signature verification to ensure the webhook comes from SendGrid: set the SENDGRID_WEBHOOK_KEY environment variable to the verification key from the Event Webhook settings.

Note: Bolt.new's WebContainer has no persistent incoming connection capability during development — SendGrid event webhooks cannot reach the Bolt preview URL. This is a core WebContainer limitation. Always configure and test webhooks using your deployed Netlify domain, not the preview URL.

```
// app/api/sendgrid/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';

interface SendGridEvent {
  email: string;
  event: string;
  timestamp: number;
  sg_event_id: string;
  sg_message_id?: string;
  reason?: string;
  status?: string;
}

export async function POST(request: NextRequest) {
  const body = await request.text();

  // Optional: verify SendGrid signature
  const webhookKey = process.env.SENDGRID_WEBHOOK_KEY;
  if (webhookKey) {
    const signature = request.headers.get('X-Twilio-Email-Event-Webhook-Signature');
    const timestamp = request.headers.get('X-Twilio-Email-Event-Webhook-Timestamp');
    // In production, verify using @sendgrid/eventwebhook package
    // For now, log a warning if signature is missing
    if (!signature || !timestamp) {
      console.warn('SendGrid webhook missing signature headers');
    }
  }

  const events: SendGridEvent[] = JSON.parse(body);

  for (const event of events) {
    switch (event.event) {
      case 'delivered':
        console.log(`Email delivered to ${event.email}`);
        break;
      case 'bounce':
        console.error(`Email bounced for ${event.email}: ${event.reason}`);
        // TODO: Mark email as invalid in your database
        break;
      case 'spamreport':
        console.warn(`Spam report from ${event.email}`);
        // TODO: Unsubscribe this email address
        break;
      case 'unsubscribe':
        console.log(`Unsubscribe from ${event.email}`);
        // TODO: Update user preference in database
        break;
      case 'open':
        console.log(`Email opened by ${event.email}`);
        break;
    }
  }

  // Always return 200 — SendGrid retries on non-200 responses
  return NextResponse.json({ received: events.length });
}
```

**Expected result:** A deployed Netlify app with SendGrid email sending in production and a webhook endpoint that receives delivery events.

## Best practices

- Always store your SendGrid API key in server-side environment variables only — never in NEXT_PUBLIC_ prefixed variables or client-side code
- Use the minimum required API key scope — 'Mail Send' only — rather than Full Access, to limit potential damage if the key is accidentally exposed
- Add a plain text version (`text` field) to every email in addition to HTML — many email clients display plain text and it improves deliverability scores
- Complete Domain Authentication (not just Single Sender Verification) for production apps — authenticated domains have significantly better inbox placement rates
- Handle bounce and spam report webhook events to remove invalid addresses from future sends — repeatedly sending to bounced addresses hurts your sender reputation
- Return HTTP 200 immediately from webhook handlers and process events asynchronously — SendGrid retries webhooks that receive non-200 responses, causing duplicate processing
- Use SendGrid's Activity Feed for debugging delivery issues — it shows the status of every email sent with timestamps, errors, and detailed delivery logs
- For high-volume sending (1,000+ emails/day), implement rate limiting on your API route and consider SendGrid's IP warming process for new dedicated IPs

## Use cases

### Contact Form Email Notification

When a user submits a contact form on your site, send an email notification to your business inbox with the user's name, email, and message. Optionally send a confirmation email back to the user with an expected response time.

Prompt example:

```
Add a contact form to my Next.js app that sends email notifications via SendGrid. Create an API route at /api/contact that accepts POST with name, email, and message fields. Use @sendgrid/mail to send a notification email to process.env.CONTACT_EMAIL_TO with the form data, and a confirmation email back to the submitter saying their message was received. Use process.env.SENDGRID_API_KEY. Add a React contact form component with validation, loading state, and success/error messages.
```

### User Signup Confirmation Email

After a user creates an account, send a branded welcome email using a SendGrid dynamic template. The template handles the HTML formatting so your API just passes the template ID and dynamic variables like the user's name and their sign-up date.

Prompt example:

```
When a user successfully signs up using Supabase Auth in my app, trigger a welcome email via SendGrid. Create an API route at /api/email/welcome that accepts POST with email, name, and plan fields. Use @sendgrid/mail with a SendGrid dynamic template ID from process.env.SENDGRID_WELCOME_TEMPLATE_ID, passing the user's name and a getting-started link as template variables. Call this API route from the signup success handler in the frontend.
```

### Order Confirmation Receipt

Send a detailed order receipt email after a successful Stripe payment. The email includes the order ID, items purchased, prices, and a thank-you message. Use SendGrid's dynamic template for a professional, branded receipt layout.

Prompt example:

```
Create an order confirmation email system. After a successful Stripe checkout.session.completed webhook event, call a sendOrderConfirmation function that uses @sendgrid/mail to send an order receipt email. The email should include the customer name, order ID, list of items with prices, total amount, and expected delivery date. Use a SendGrid dynamic template (ID from SENDGRID_ORDER_TEMPLATE_ID env var) with templateData containing all order fields.
```

## Troubleshooting

### Getting 'The from address does not match a verified Sender Identity' error

Cause: The email address in SENDGRID_FROM_EMAIL has not been verified in SendGrid. SendGrid requires all sending addresses or domains to be verified via Single Sender Verification or Domain Authentication before email can be sent.

Solution: Go to app.sendgrid.com → Settings → Sender Authentication → Get Started under Single Sender Verification. Enter the email address you are using as the from address and complete the verification via the confirmation email. For production, set up Domain Authentication instead to verify your entire domain.

### SendGrid API key shows as undefined in server logs even though it is set in .env.local

Cause: Next.js environment variables in .env.local only load in the local development server. After deploying to Netlify, the variables must be added separately in Netlify's environment variable settings — .env.local is never committed or deployed.

Solution: In Netlify dashboard → Site Configuration → Environment Variables → Add variable, add SENDGRID_API_KEY with your key value. Click Trigger Deploy → Deploy site to apply the new variables. The .env.local file exists only for local development and is gitignored by Next.js by default.

### Emails are being sent successfully (no API error) but never arrive in the inbox

Cause: Three common causes: (1) emails are in spam/junk — check there first, (2) the sending domain is not authenticated (Domain Authentication not configured), causing poor deliverability, or (3) the recipient email address has a soft bounce or the address is invalid.

Solution: Check SendGrid Activity Feed (app.sendgrid.com → Activity) to see the delivery status. If the status is 'Delivered', the email reached the recipient's mail server — check spam folders. If 'Blocked' or 'Bounced', the Activity log shows the reason. For consistent inbox delivery, complete Domain Authentication in SendGrid Settings → Sender Authentication.

### SendGrid event webhooks are not triggering during Bolt.new development

Cause: Bolt.new's WebContainer does not have a persistent public URL that can receive incoming HTTP connections. SendGrid webhooks send POST requests to your specified URL — this requires a deployed, publicly accessible endpoint. The dynamic WebContainer preview URL is not a valid webhook target.

Solution: Deploy your app to Netlify first (click Deploy in Bolt.new), then configure the SendGrid Event Webhook URL to point to your Netlify domain: https://your-app.netlify.app/api/sendgrid/webhook. Webhooks cannot be tested in the Bolt preview — this is a fundamental WebContainer limitation.

## Frequently asked questions

### Can I send emails from Bolt.new's WebContainer preview without deploying?

Yes, for outbound email sending. The @sendgrid/mail package makes outbound API calls to SendGrid's servers, which works in Bolt's WebContainer. You can build and test email sending in the preview. The only email feature that requires deployment is SendGrid's Event Webhook, which sends incoming POST requests that the WebContainer cannot receive.

### Is SendGrid the same as Twilio SendGrid?

Yes. Twilio acquired SendGrid in 2019 and the product is now officially called Twilio SendGrid. However, it continues to operate as a separate product with its own API, SDK (@sendgrid/mail), dashboard, and pricing. The npm package is @sendgrid/mail and the API endpoint is api.sendgrid.com — not Twilio's API domain.

### How do I prevent my SendGrid emails from going to spam?

The most important step is completing Domain Authentication in SendGrid Settings — this adds DKIM, SPF, and DMARC DNS records to your domain, which email providers use to verify sender legitimacy. Also ensure your email content is not spam-like, include a plain text version alongside HTML, add an unsubscribe link for marketing emails, and warm up sending volume gradually for new domains.

### How do I use SendGrid dynamic templates in Bolt.new?

Create your template in SendGrid's dashboard (Email API → Dynamic Templates), copy the template ID (starts with d-), and add it as SENDGRID_WELCOME_TEMPLATE_ID in your environment variables. In your API route, pass the templateId and a dynamicTemplateData object with your template's handlebars variables. Bolt.new's API routes handle this server-side using the @sendgrid/mail package.

### What is the SendGrid free tier limit?

SendGrid's free tier allows 100 emails per day and 40,000 emails in the first 30 days, with no credit card required. After the first month, the limit drops to 100 emails/day on the free plan. For most small apps and contact forms, this is sufficient. The Essentials plan ($19.95/month) allows 50,000 emails/month.

### Can Bolt.new handle SendGrid webhooks for email events like bounces and opens?

Yes, after deployment. SendGrid event webhooks send POST requests to your registered endpoint. In Bolt.new's WebContainer, you can write the webhook handler code and test it manually, but actual webhook delivery from SendGrid requires a publicly accessible URL. Deploy your app to Netlify or Bolt Cloud, register that URL in SendGrid's Event Webhook settings, and webhook events will flow to your handler.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/sendgrid
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/sendgrid
