# How to Integrate Bolt.new with Mailchimp

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

## TL;DR

To integrate Mailchimp with Bolt.new, add your Mailchimp API key and server prefix to server-side environment variables, then create Next.js API routes that call Mailchimp's Marketing API to add subscribers, manage audiences, and send campaigns. The Mailchimp JavaScript embed signup form works in Bolt's WebContainer preview. API calls for subscriber management and campaign analytics also work from the preview. Webhooks for unsubscribes and bounce handling require a deployed URL.

## Adding Email Marketing to Bolt.new Apps with Mailchimp

Mailchimp is the most widely used email marketing platform, making it a common integration requirement for Bolt.new apps that need newsletter signup functionality, transactional email sequences, or marketing campaign management. The integration covers two main patterns: programmatic subscriber management through the Marketing API (adding subscribers, updating contact info, triggering automations) and the JavaScript embed widget for drop-in signup forms.

Mailchimp's Marketing API uses simple HTTP Basic Auth where the username is the string 'anystring' (literally any string works) and the password is your API key. This unusual convention is well-documented and makes implementation straightforward — no OAuth, no token refresh, no expiration to manage. The one Mailchimp-specific complexity is the server prefix: your API key ends in a datacenter identifier like '-us21' or '-us6', and your API calls must go to that specific datacenter's URL (https://us21.api.mailchimp.com/3.0/). Getting the server prefix wrong is the most common source of API errors.

Mailchimp's free plan includes 500 contacts and 1,000 sends per month — generous for building and testing a Bolt integration. The API is fully available on the free plan, including subscriber management, audience data, and campaign analytics. Paid plans (Essentials from $13/month) add send time optimization, A/B testing, and remove Mailchimp branding from emails. For a typical Bolt app with a newsletter signup form and basic audience management, the free plan covers everything you need during development and early production.

## Before you start

- A Mailchimp account (free plan available at mailchimp.com — 500 contacts, 1,000 sends/month)
- A Mailchimp API key (Account → Extras → API Keys → Create A Key)
- Your Mailchimp server prefix — the datacenter suffix from your API key (e.g., 'us21' if your key ends in '-us21')
- Your Mailchimp Audience (List) ID (Audience → Manage Audience → Settings → Audience name and defaults → Audience ID)
- A Bolt.new project using Next.js for server-side API routes

## Step-by-step guide

### 1. Get Your Mailchimp API Key and Audience ID

Mailchimp requires two pieces of information: an API key for authentication and an Audience ID (also called List ID) to identify which subscriber list to use. To get your API key, log into Mailchimp, click your profile avatar in the top-right, select 'Profile', then go to 'Extras' → 'API keys'. Click 'Create A Key'. Give it a name like 'Bolt App Key'. The key is shown as a long string ending in a datacenter identifier — for example: 'abc123def456ghi789-us21'. The part after the last hyphen ('us21' in this example) is your server prefix. Copy the full key and note the prefix separately. For the Audience ID, go to Audience → All contacts → Settings (gear icon) → Audience name and defaults. Look for 'Audience ID' — it's an 8-10 character alphanumeric string like 'a1b2c3d4e'. This is your list ID for all subscriber management calls. If you have multiple audiences, note the IDs for each. The server prefix is critical: Mailchimp's API endpoint includes the datacenter (e.g., https://us21.api.mailchimp.com/3.0/). Using the wrong datacenter returns a 400 error with 'API key is missing or invalid'. Store the full API key, server prefix, and list ID as server-side environment variables.

```
# .env
MAILCHIMP_API_KEY=your_api_key_ending_in_-us21
MAILCHIMP_SERVER_PREFIX=us21
MAILCHIMP_LIST_ID=your_audience_id
```

**Expected result:** Your Mailchimp API key, server prefix, and audience list ID are stored in .env. You can verify the API key works with: curl -u anystring:YOUR_API_KEY https://USX.api.mailchimp.com/3.0/ping

### 2. Create the Mailchimp API Helper and Subscribe Route

Build a centralized Mailchimp API helper and the subscriber management route. The Mailchimp Marketing API uses HTTP Basic Auth where the username can be literally any string (the API ignores it) and the password is your API key. The base URL must match your server prefix. The most important route is subscriber management — adding, updating, and reading contact records. Mailchimp uses the MD5 hash of the subscriber's lowercase email address as the unique contact identifier, which means you can add or update a subscriber without knowing their Mailchimp contact ID in advance. For adding subscribers, the PUT method on /lists/{listId}/members/{emailHash} acts as upsert — if the contact exists, it updates; if not, it creates. The subscriber status is critical: 'subscribed' adds them to active send lists, 'pending' requires double opt-in confirmation (sends a confirmation email), and 'unsubscribed' removes them from active sends while keeping the contact record. For most signup forms, use 'subscribed' for immediate opt-in or 'pending' for double opt-in, which is required in some countries (GDPR compliance). Always store first and last name under the FNAME and LNAME merge fields — these are Mailchimp's default personalization variables used in campaign templates.

```
// lib/mailchimp.ts
import crypto from 'crypto';

export function getMailchimpBase() {
  return `https://${process.env.MAILCHIMP_SERVER_PREFIX}.api.mailchimp.com/3.0`;
}

export function getMailchimpAuth() {
  return Buffer.from(`anystring:${process.env.MAILCHIMP_API_KEY}`).toString('base64');
}

export function emailToHash(email: string): string {
  return crypto.createHash('md5').update(email.toLowerCase().trim()).digest('hex');
}

export async function mailchimpAPI<T>(
  endpoint: string,
  options: RequestInit = {}
): Promise<T> {
  const url = `${getMailchimpBase()}${endpoint}`;
  const response = await fetch(url, {
    ...options,
    headers: {
      Authorization: `Basic ${getMailchimpAuth()}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  const data = await response.json();
  if (!response.ok) {
    throw new Error(data.detail || data.title || `Mailchimp API error: ${response.status}`);
  }
  return data;
}

// app/api/mailchimp/subscribe/route.ts
import { NextResponse } from 'next/server';
import { mailchimpAPI, emailToHash } from '@/lib/mailchimp';

export async function POST(request: Request) {
  try {
    const { email, firstName = '', lastName = '' } = await request.json();

    if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      return NextResponse.json({ error: 'Valid email is required' }, { status: 400 });
    }

    const emailHash = emailToHash(email);
    const listId = process.env.MAILCHIMP_LIST_ID!;

    await mailchimpAPI(`/lists/${listId}/members/${emailHash}`, {
      method: 'PUT',
      body: JSON.stringify({
        email_address: email.toLowerCase().trim(),
        status_if_new: 'subscribed',
        status: 'subscribed',
        merge_fields: {
          FNAME: firstName,
          LNAME: lastName,
        },
      }),
    });

    return NextResponse.json({ success: true, message: 'Successfully subscribed!' });
  } catch (error: unknown) {
    const e = error as { message: string };
    return NextResponse.json({ error: e.message }, { status: 500 });
  }
}
```

**Expected result:** POST to /api/mailchimp/subscribe with a valid email adds the contact to your Mailchimp audience. The subscriber appears in your Mailchimp audience immediately. Existing subscribers are updated without error.

### 3. Embed Mailchimp Signup Form Widget

Mailchimp provides an embeddable JavaScript signup form widget that can be added directly to your Bolt app without any backend code. This widget handles form rendering, validation, CAPTCHA (on some plans), and API submission entirely client-side. The embed works in Bolt's WebContainer preview immediately. To get the embed code, log into Mailchimp, go to Audience → Signup forms → Embedded forms. Customize the form style (Classic, Super Slim, or Naked for custom CSS), select which fields to show, and copy the generated HTML/script embed code. In a Next.js app, render the embed inside a component using dangerouslySetInnerHTML for the script tag, or use next/script to load it asynchronously. The Mailchimp embed form submits to Mailchimp's servers directly, bypassing your backend entirely. This approach has trade-offs: it's simpler to implement but gives you less control over the post-submission experience and you can't intercept or validate data before it reaches Mailchimp. For most landing page newsletter signups, the embed widget is the fastest approach. For signup forms that need custom logic (adding UTM parameters, tagging subscribers based on which page they signed up on, or syncing to your database), use the API route approach from the previous step.

```
// components/NewsletterSignup.tsx
'use client';
import { useEffect, useRef } from 'react';

export function NewsletterSignup() {
  const formRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // Mailchimp's embed script modifies the form after load
    // Using a ref ensures it runs only on the client
    const script = document.createElement('script');
    script.src = '//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js';
    script.async = true;
    if (formRef.current) {
      formRef.current.appendChild(script);
    }
    return () => {
      if (formRef.current && script.parentNode === formRef.current) {
        formRef.current.removeChild(script);
      }
    };
  }, []);

  return (
    <div ref={formRef} className="max-w-md">
      {/* Replace ACTION_URL with your Mailchimp form action URL from the embed code */}
      <form
        action="https://YOURLIST.us21.list-manage.com/subscribe/post?u=XXXXX&id=YYYYY"
        method="post"
        className="flex gap-2"
      >
        <input
          type="email"
          name="EMAIL"
          placeholder="Your email address"
          required
          className="flex-1 px-3 py-2 border rounded-md text-sm"
        />
        {/* Anti-spam honeypot field - do not remove */}
        <div style={{ position: 'absolute', left: '-5000px' }} aria-hidden="true">
          <input type="text" name="b_XXXXX_YYYYY" tabIndex={-1} defaultValue="" />
        </div>
        <button
          type="submit"
          className="px-4 py-2 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700"
        >
          Subscribe
        </button>
      </form>
      <p className="text-xs text-gray-500 mt-2">
        We respect your privacy. Unsubscribe at any time.
      </p>
    </div>
  );
}
```

**Expected result:** The newsletter signup form renders correctly in the Bolt preview. Submitting a valid email adds the subscriber to your Mailchimp audience. The honeypot field is present but invisible to human users.

### 4. Fetch Campaign Analytics and Configure Webhooks

With subscriber management working, add campaign analytics fetching to give your team visibility into email performance. The Mailchimp Campaigns API returns all sent campaigns with their report statistics — open rate, click rate, unsubscribe rate, bounce rate, and total recipients. Fetch the campaign list and sort by send date for a marketing performance dashboard. For real-time event tracking — when a subscriber unsubscribes, hard bounces, clicks a link, or opens an email — Mailchimp provides webhooks. These send POST requests to your endpoint when events occur. Like all webhook-based integrations in Bolt, incoming webhook requests cannot reach Bolt's WebContainer. Deploy to Netlify or Bolt Cloud, then configure your Mailchimp webhook URL in Audience → Manage Audience → Settings → Webhooks. Add your deployed endpoint URL (e.g., https://your-app.netlify.app/api/mailchimp/webhook) and select the events to track. Processing unsubscribe webhooks is particularly important for GDPR compliance — when a user unsubscribes in Mailchimp, your webhook handler should mark them as unsubscribed in your application database to prevent re-subscribing them without consent.

```
// app/api/mailchimp/campaigns/route.ts
import { NextResponse } from 'next/server';
import { mailchimpAPI } from '@/lib/mailchimp';

interface MailchimpCampaign {
  id: string;
  subject_line: string;
  send_time: string;
  emails_sent: number;
  report_summary: {
    open_rate: number;
    click_rate: number;
    unsubscribes: number;
  };
}

interface CampaignsResponse {
  campaigns: MailchimpCampaign[];
}

export async function GET() {
  try {
    const data = await mailchimpAPI<CampaignsResponse>(
      '/campaigns?status=sent&count=10&sort_field=send_time&sort_dir=DESC'
    );

    const campaigns = data.campaigns.map((c) => ({
      id: c.id,
      subject: c.subject_line,
      sentAt: c.send_time,
      recipients: c.emails_sent,
      openRate: ((c.report_summary?.open_rate || 0) * 100).toFixed(1) + '%',
      clickRate: ((c.report_summary?.click_rate || 0) * 100).toFixed(1) + '%',
      unsubscribes: c.report_summary?.unsubscribes || 0,
    }));

    return NextResponse.json(campaigns);
  } catch (error: unknown) {
    const e = error as { message: string };
    return NextResponse.json({ error: e.message }, { status: 500 });
  }
}
```

**Expected result:** GET to /api/mailchimp/campaigns returns a list of sent campaigns with formatted open and click rates. After deploying and configuring webhooks, unsubscribe events update subscriber status in the database.

## Best practices

- Store MAILCHIMP_API_KEY as a server-side environment variable (no NEXT_PUBLIC_ prefix) — it gives full read and write access to your subscriber lists and campaign data
- Always use PUT (upsert) instead of POST when adding subscribers to avoid errors for contacts who are already in your audience
- Include status_if_new in subscriber upserts so you set the subscription status for new contacts without overwriting the status of existing ones who may have unsubscribed
- Always include the honeypot anti-spam field in embedded signup forms — removing it increases bot submissions significantly
- Tag subscribers based on where they signed up (landing page, checkout, sidebar) using the tags array in the PUT body to enable audience segmentation in Mailchimp
- Handle Mailchimp unsubscribe webhooks to keep your application database in sync — this is required for GDPR compliance to prevent re-subscribing users who explicitly unsubscribed
- Use the server prefix from your API key (the datacenter suffix after the last hyphen) in your API base URL — using the wrong datacenter is the most common Mailchimp integration error
- Comply with email marketing laws (CAN-SPAM, GDPR, CASL) by including an unsubscribe link in all campaign emails — Mailchimp includes this automatically in campaign templates

## Use cases

### Newsletter Signup Form

Add a newsletter signup form to your Bolt app that adds new subscribers directly to a Mailchimp audience. Users enter their email (and optionally name) and click subscribe. Your API route validates the email and calls Mailchimp's API to add or update the contact. Show success confirmation with a message about what to expect.

Prompt example:

```
Add a newsletter signup form to my landing page. Create a form component with email input and optional first name field. On submit, call /api/mailchimp/subscribe which adds the subscriber to my Mailchimp audience using MAILCHIMP_API_KEY, MAILCHIMP_SERVER_PREFIX, and MAILCHIMP_LIST_ID. Handle the case where the email is already subscribed gracefully (don't show an error, just show the same success message). Show a success message: 'Thanks for subscribing! Check your inbox for a confirmation email.' Show a loading state on the button during submission.
```

### Email Analytics Dashboard

Build an internal dashboard that pulls Mailchimp campaign performance metrics — open rates, click rates, unsubscribe rates, and top-performing links — from the Mailchimp API. Display the last 10 campaigns as a table with their stats, letting your marketing team review performance without logging into Mailchimp.

Prompt example:

```
Create a marketing analytics page at /admin/email-analytics. Fetch the last 10 campaigns from /api/mailchimp/campaigns which calls Mailchimp's Campaigns API. Show a table with columns: Campaign Name, Send Date, Recipients, Open Rate (%), Click Rate (%), and Unsubscribes. Add a detail view that shows the top clicked links when a campaign row is expanded. Sort campaigns by send date descending. Show a 'No campaigns sent yet' empty state if the list is empty.
```

### Segmented Audience Management

Build a subscriber management interface that shows audience segments, lets you create tags for subscribers, and bulk-updates contact properties. Useful for managing lists from a custom CRM or app that uses Mailchimp as the email sending layer.

Prompt example:

```
Create an audience management page at /admin/subscribers. Fetch the subscriber count and segments from /api/mailchimp/audience. Show total subscribers as a stat card. Show a table of recent subscribers (last 50) with email, name, subscription date, and tags. Add a search bar that filters by email. Allow adding tags to individual subscribers by clicking their row and using a tag input. Call /api/mailchimp/subscribers/[email]/tags (PUT) to update tags via the Mailchimp API.
```

## Troubleshooting

### Mailchimp API returns 400 'API key is missing or invalid'

Cause: The most common cause is using the wrong API URL for your datacenter. Mailchimp's API endpoint must match your server prefix (e.g., us21.api.mailchimp.com, not just api.mailchimp.com).

Solution: Check that MAILCHIMP_SERVER_PREFIX matches the datacenter suffix at the end of your API key. If your API key ends in '-us21', the prefix is 'us21' and the base URL is https://us21.api.mailchimp.com/3.0/. Test with curl using your exact prefix: curl -u anystring:YOUR_KEY https://usX.api.mailchimp.com/3.0/ping

```
// Extract server prefix from API key:
const apiKey = process.env.MAILCHIMP_API_KEY!;
const serverPrefix = apiKey.split('-').pop(); // Gets 'us21' from 'key-us21'
const baseUrl = `https://${serverPrefix}.api.mailchimp.com/3.0`;
```

### Subscriber already exists causes a 400 error when trying to subscribe

Cause: The Mailchimp API returns 400 'Member Exists' when you use POST to add a subscriber who is already in the audience.

Solution: Use PUT instead of POST for subscriber management. PUT to /lists/{listId}/members/{emailHash} is an upsert operation — it creates the contact if they don't exist and updates them if they do, without throwing an error for existing contacts. Include status_if_new in the body to set the status only for new contacts.

```
// Use PUT (upsert) instead of POST:
await mailchimpAPI(`/lists/${listId}/members/${emailHash}`, {
  method: 'PUT', // Not POST
  body: JSON.stringify({
    email_address: email,
    status_if_new: 'subscribed', // Only sets status for NEW contacts
    // Existing contacts keep their current status
  }),
});
```

### Mailchimp webhook events never arrive

Cause: Mailchimp webhooks are configured per audience and POST to your URL when events occur. During Bolt development, the WebContainer has no public URL for Mailchimp to call.

Solution: Deploy to Netlify or Bolt Cloud first. Go to Mailchimp → Audience → Manage Audience → Settings → Webhooks. Add your deployed URL (e.g., https://your-app.netlify.app/api/mailchimp/webhook). Select the event types to track. Mailchimp also provides a way to send a test notification from the webhook settings page — use this to verify your handler works after deployment.

### Embedded Mailchimp form submits but subscriber doesn't appear in the audience

Cause: The form action URL or audience ID in the embedded code is from a different Mailchimp account, or the honeypot spam trap field is being triggered by browser autofill.

Solution: Regenerate the embed code from your Mailchimp account and verify the form action URL contains your account's unique parameters. If using custom form HTML, ensure the honeypot input (the hidden field) is present but not auto-filled by the browser. Add autocomplete='off' to the form to prevent browser autofill from filling hidden fields.

```
<form autocomplete="off" ...>
  <div style={{position: 'absolute', left: '-5000px'}} aria-hidden="true">
    <input type="text" name="b_XXXXX_YYYYY" tabIndex={-1} defaultValue="" readOnly />
  </div>
</form>
```

## Frequently asked questions

### What's the difference between Mailchimp and SendGrid?

Mailchimp is for marketing email campaigns — newsletters, promotions, and automated marketing sequences sent to your subscriber audience on a schedule or trigger. SendGrid is for transactional email — receipts, order confirmations, password resets, and notification emails triggered by specific user actions. Many apps use both: Mailchimp for marketing campaigns and SendGrid for system-triggered emails.

### Is Mailchimp free for Bolt.new development?

Yes. Mailchimp's free plan includes 500 contacts, 1,000 sends per month, and full API access. This is sufficient for building and testing a Bolt.new integration. The free plan includes the Marketing API, subscriber management, campaign creation, and basic analytics. Paid plans (starting at $13/month) add more contacts, more sends, and advanced features like send time optimization.

### How do I find my Mailchimp Audience ID?

In Mailchimp, go to Audience → All contacts → Settings (gear icon in the top-right of the Audience section) → Audience name and defaults. Scroll to the bottom of the page and look for 'Audience ID' — it's an 8-10 character alphanumeric string. If you have multiple audiences, switch between them using the audience selector at the top of the page. Store this as MAILCHIMP_LIST_ID in your .env file.

### Can I add subscribers without the double opt-in confirmation email?

Yes. Use status='subscribed' (not 'pending') when adding contacts via the API to bypass double opt-in. This adds the contact directly as subscribed without sending a confirmation email. Note that double opt-in is required by law in some countries (particularly Germany) and strongly recommended for GDPR compliance. Mailchimp accounts are sometimes flagged for directly adding subscribers without opt-in if bounce and complaint rates are high.

### Why does the Mailchimp API use 'anystring' as the Basic Auth username?

Mailchimp's Basic Auth convention uses the API key as the password and ignores the username field entirely. You can pass any value as the username — the string 'anystring' is used in Mailchimp's own documentation examples, but 'apikey', 'user', or any other string works equally well. What matters is that the password field contains a valid API key. This is a legacy API design decision from Mailchimp's early days.

---

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