# How to Integrate Bolt.new with Constant Contact

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

## TL;DR

Constant Contact integrates with Bolt.new through a Next.js API route using OAuth 2.0. Register a developer app to get a client ID and secret, handle the OAuth callback on your deployed site (not in the WebContainer preview), store the access token in .env, and call Constant Contact's v3 REST API to manage contact lists, add subscribers, and create email campaigns.

## Constant Contact Email Marketing Integration for Bolt.new Apps

Constant Contact has served small businesses and nonprofits for over two decades, building a reputation for simplicity and reliability in email marketing. Unlike platforms that cater to developers and growth engineers, Constant Contact's design philosophy centers on ease of use for non-technical users — a trait that extends to its API. The v3 REST API follows standard HTTP conventions with straightforward JSON payloads, making it accessible for developers building custom subscriber capture forms, internal campaign dashboards, or event registration integrations.

For Bolt.new apps, the most common use cases are: capturing subscribers from a website or landing page into a Constant Contact list, building a lightweight email dashboard that shows campaign performance without logging into the Constant Contact interface, and connecting event registration forms to Constant Contact for post-event follow-up sequences. The free trial gives you full API access to test your integration before committing to a paid plan.

The key technical consideration for this integration is OAuth 2.0 authentication. Constant Contact requires a registered developer app with a redirect URI that Constant Contact can call back to after the user authorizes your app. Because Bolt's WebContainer runs in a browser sandbox without a stable public URL, the OAuth callback cannot be completed in the development preview — you must deploy to Netlify or Bolt Cloud first, register that deployed URL as your redirect URI in Constant Contact's developer portal, and complete the OAuth flow there. Once you have an access token stored in your .env file, all subsequent API calls work normally in both development and production.

## Before you start

- A Constant Contact account with a free trial or paid plan — the free trial provides full API access for testing
- A registered Constant Contact developer app at developer.constantcontact.com with an OAuth redirect URI pointing to your deployed site (e.g., https://your-app.netlify.app/api/auth/callback/constant-contact)
- Your Constant Contact app's client ID and client secret from the developer portal
- A Bolt.new project using Next.js (for API routes that keep the access token server-side)
- Your deployed site URL (Netlify or Bolt Cloud) to complete the OAuth flow — OAuth callbacks do not work in Bolt's WebContainer preview

## Step-by-step guide

### 1. Register a Constant Contact developer app and configure OAuth

Before writing any code, you need to register a developer application in Constant Contact's developer portal. This creates the OAuth credentials (client ID and secret) your Bolt.new app will use to authenticate with the Constant Contact API.

Go to developer.constantcontact.com and sign in with your Constant Contact account. Click 'My Applications' and then 'New Application.' Give your app a name (for example, 'My Bolt Marketing App') and a brief description. The most important field is the Redirect URI — this is where Constant Contact will redirect the user after they authorize your app. Since Bolt's WebContainer runs in a browser sandbox without a stable public URL, you must use your deployed site's URL here: for example, https://your-app.netlify.app/api/auth/callback/constant-contact. You cannot use localhost or the Bolt preview URL as a redirect URI.

After creating the app, Constant Contact shows you an API Key (this is your client ID) and a Client Secret. Copy both values. The client ID is safe to use in slightly more visible places, but the client secret must be kept server-side only and stored in .env as CONSTANT_CONTACT_CLIENT_SECRET.

Constant Contact's OAuth 2.0 flow works like standard OAuth: your app redirects the user to Constant Contact's authorization URL, the user logs in and grants permission, Constant Contact redirects back to your redirect URI with an authorization code, and your app exchanges the code for an access token. The access token is then used as a Bearer token in all API calls. Access tokens have a long expiration, and Constant Contact also provides a refresh token for obtaining new access tokens without re-authorizing.

For Bolt.new integrations where you are the only user (a backend integration for your own account), you can also use the Constant Contact Authorization Code Flow manually: open the authorization URL in a browser, authorize, capture the code from the redirect URL, and exchange it for an access token in a one-time script. This avoids building a full OAuth UI in your app.

```
# .env
# Constant Contact developer app credentials
# Get from developer.constantcontact.com → My Applications
CONSTANT_CONTACT_CLIENT_ID=your_client_id_here
CONSTANT_CONTACT_CLIENT_SECRET=your_client_secret_here

# Access token obtained after completing OAuth flow
# Required for all API calls — server-side only, never NEXT_PUBLIC_
CONSTANT_CONTACT_ACCESS_TOKEN=your_access_token_here

# Constant Contact list ID to add new subscribers to
# Find in Constant Contact → Contacts → Lists → click a list → copy ID from URL
CONSTANT_CONTACT_LIST_ID=your_list_id_here
```

**Expected result:** You have a registered Constant Contact developer app with client ID, client secret, and an access token in .env. The redirect URI in the developer portal matches your deployed site URL.

### 2. Build the Constant Contact API service and subscriber route

With credentials in place, scaffold the Constant Contact API service layer. Create a utility module at lib/constant-contact.ts that handles authentication headers and wraps the most commonly used v3 API endpoints. Then create Next.js API routes that the React frontend calls to add subscribers and manage contact lists.

The Constant Contact v3 API base URL is https://api.cc.email/v3. Authentication uses a Bearer token in the Authorization header — the access token you obtained via OAuth. The primary endpoints for subscriber management are: GET /contact_lists (retrieve all lists with their IDs and names), POST /contacts (create or update a contact, optionally adding them to lists), GET /contacts (search contacts by email), and GET /contact_lists/{list_id}/contacts (list members of a specific list).

The POST /contacts endpoint supports an important feature called 'create_or_update' via the update_source field. Setting update_source to 'Account' with an existing email address will update the contact rather than returning a duplicate error — critical for form-based subscriber capture where the same user might submit multiple times.

Paste the Bolt prompt below to generate the API service and subscriber route. After generation, verify that lib/constant-contact.ts reads CONSTANT_CONTACT_ACCESS_TOKEN from process.env and that no token value appears in any client-side file. The subscriber form component should call /api/constant-contact/subscribe rather than calling the Constant Contact API directly — direct client-side calls expose your access token in browser network requests.

One important note about the WebContainer development environment: outbound HTTP calls to api.cc.email work fine in Bolt's preview. You can test the subscriber capture form in development and see contacts appear in your Constant Contact account in real time. The OAuth callback flow, however, requires the deployed site.

```
// lib/constant-contact.ts
const CC_BASE = 'https://api.cc.email/v3';

async function ccFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  const token = process.env.CONSTANT_CONTACT_ACCESS_TOKEN;
  if (!token) throw new Error('CONSTANT_CONTACT_ACCESS_TOKEN not configured');

  const res = await fetch(`${CC_BASE}${endpoint}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if (!res.ok) {
    const error = await res.json().catch(() => ({ message: res.statusText }));
    throw Object.assign(new Error(error.error_message ?? 'Constant Contact API error'), {
      status: res.status,
      body: error,
    });
  }

  return res.json();
}

export interface CCContactPayload {
  email_address: { address: string; permission_to_send: string };
  first_name?: string;
  last_name?: string;
  list_memberships?: string[];
  update_source?: string;
}

export async function addSubscriber(
  email: string,
  firstName: string,
  lastName: string,
  listId?: string
) {
  const payload: CCContactPayload = {
    email_address: { address: email, permission_to_send: 'implicit' },
    first_name: firstName,
    last_name: lastName,
    update_source: 'Account',
  };

  const targetList = listId ?? process.env.CONSTANT_CONTACT_LIST_ID;
  if (targetList) {
    payload.list_memberships = [targetList];
  }

  return ccFetch('/contacts', { method: 'POST', body: JSON.stringify(payload) });
}

export async function getLists() {
  return ccFetch('/contact_lists?include_count=true&status=ACTIVE');
}

export async function getCampaigns(limit = 10) {
  return ccFetch(`/emails?limit=${limit}&status=SENT`);
}
```

**Expected result:** lib/constant-contact.ts provides authenticated access to the Constant Contact v3 API. The /api/constant-contact/subscribe route creates or updates contacts. Submitting the newsletter form in the Bolt preview adds a real subscriber to your Constant Contact list.

### 3. Handle OAuth 2.0 callback on the deployed site

For applications where users will authenticate with their own Constant Contact accounts (rather than using your own token), you need a full OAuth 2.0 implementation with an authorization callback route. This step is also required if you need to refresh expired access tokens programmatically.

The OAuth flow has four steps. First, your app constructs an authorization URL: https://authz.constantcontact.com/oauth2/default/v1/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope=contact_data+campaign_data. The scope parameter controls what permissions you're requesting — contact_data gives access to contacts and lists, campaign_data gives access to campaigns and analytics. Second, the user is redirected to this URL and logs into their Constant Contact account. Third, after authorization, Constant Contact redirects back to your redirect URI with a code query parameter (for example: https://your-app.netlify.app/api/auth/callback/constant-contact?code=ABC123). Fourth, your callback route makes a server-side POST request to Constant Contact's token endpoint to exchange the code for access and refresh tokens.

This callback must be handled by a real server — it cannot work in Bolt's WebContainer preview because the preview URL is a dynamic StackBlitz-generated URL that changes between sessions and cannot be registered as a stable redirect URI in the developer portal. Deploy to Netlify or Bolt Cloud first, register the production URL as your redirect URI, and test the OAuth flow on the deployed site.

For single-user integrations (connecting your own Constant Contact account to your Bolt app), the simpler approach is to complete the OAuth flow once in a browser, capture the access token, store it in your hosting environment variables, and skip building the callback route entirely. The Constant Contact Developer Portal provides an OAuth Playground for exactly this purpose.

```
// app/api/auth/callback/constant-contact/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get('code');

  if (!code) {
    return NextResponse.json({ error: 'Missing authorization code' }, { status: 400 });
  }

  const clientId = process.env.CONSTANT_CONTACT_CLIENT_ID!;
  const clientSecret = process.env.CONSTANT_CONTACT_CLIENT_SECRET!;
  const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback/constant-contact`;

  // Exchange code for tokens
  const tokenRes = await fetch(
    'https://authz.constantcontact.com/oauth2/default/v1/token',
    {
      method: 'POST',
      headers: {
        Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code,
        redirect_uri: redirectUri,
      }),
    }
  );

  if (!tokenRes.ok) {
    const err = await tokenRes.json();
    return NextResponse.json({ error: err }, { status: 400 });
  }

  const tokens = await tokenRes.json();
  // In production: store tokens in your database or session
  // For development: log the access token to manually add to .env
  console.log('Access token:', tokens.access_token);

  return NextResponse.redirect(new URL('/dashboard', request.url));
}
```

**Expected result:** The OAuth callback route exchanges the authorization code for tokens when Constant Contact redirects back after user authorization. The flow works on the deployed site but not in the Bolt WebContainer preview (by design — the preview has no stable callback URL).

### 4. Create a campaign analytics dashboard

One of the most valuable things you can build with the Constant Contact API is a campaign performance dashboard that surfaces email analytics without requiring access to the Constant Contact platform. This is useful for clients, team members who need read-only access, or integrating email analytics alongside other business metrics in a unified dashboard.

The Constant Contact v3 API provides campaign data through several endpoints. GET /emails returns a paginated list of email campaigns with basic metadata: campaign name, subject line, status (SENT, SCHEDULED, DRAFT), sent date, and unique counts for opens and clicks at the summary level. For deeper analytics on a specific campaign, GET /reports/summary_reports?campaign_ids={id} returns detailed metrics including opens, clicks, bounces, unsubscribes, and forwarding data.

Build the dashboard in two layers: a campaign list view showing all sent campaigns with top-line metrics, and a detail view that shows the full analytics report for a selected campaign. The list view should be sortable by sent date, open rate, and click rate to help identify top-performing and underperforming campaigns.

For displaying analytics data, Recharts is the recommended charting library for Bolt-generated Next.js apps. A bar chart comparing open rates across recent campaigns provides immediate visual context. Color-code open rate bars above 25% as green (industry average) and below as orange or red — this immediately highlights campaigns that need attention.

API responses are paginated — Constant Contact returns up to 500 campaigns per page with a cursor-based next link for additional pages. For most small business accounts, the first page covers all recent campaigns. Handle the pagination cursor in the API route so the React component can request additional pages if needed.

```
// app/api/constant-contact/campaigns/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const token = process.env.CONSTANT_CONTACT_ACCESS_TOKEN;
  if (!token) {
    return NextResponse.json({ error: 'Constant Contact not configured' }, { status: 500 });
  }

  const { searchParams } = new URL(request.url);
  const limit = searchParams.get('limit') ?? '25';

  const res = await fetch(
    `https://api.cc.email/v3/emails?status=SENT&limit=${limit}`,
    { headers: { Authorization: `Bearer ${token}` } }
  );

  if (!res.ok) {
    return NextResponse.json({ error: 'Failed to fetch campaigns' }, { status: res.status });
  }

  const data = await res.json();

  const campaigns = (data.campaigns ?? []).map((c: Record<string, unknown>) => ({
    id: c.campaign_id,
    name: c.name,
    subject: (c.campaign_activities as Array<{ subject?: string }>)?.[0]?.subject ?? '',
    status: c.current_status,
    sentAt: c.updated_at,
  }));

  return NextResponse.json({ campaigns, next: data._links?.next?.href ?? null });
}
```

**Expected result:** The campaign dashboard displays all sent Constant Contact campaigns with a bar chart comparing open and click rates. Clicking a campaign shows detailed analytics including bounces, unsubscribes, and forward counts — all without logging into Constant Contact.

## Best practices

- Always use the update_source: 'Account' parameter when POSTing to /contacts to enable upsert behavior — this prevents duplicate contacts when the same email submits your form multiple times
- Store the Constant Contact access token in server-side .env only — it grants full access to your contact database and campaign management, so it must never appear in client-side JavaScript or be prefixed with NEXT_PUBLIC_
- Complete the OAuth flow on your deployed site, not in Bolt's WebContainer preview — the preview uses a dynamic URL that cannot be registered as a stable OAuth redirect URI
- Request only the OAuth scopes you need (contact_data for subscriber management, campaign_data for analytics) — avoid requesting broad permissions that could be misused
- Implement access token refresh logic for production apps so the integration does not silently break when the token expires — store both access_token and refresh_token in your environment configuration
- Use Constant Contact's batch contact import endpoints for bulk subscriber operations rather than making individual POST /contacts calls to stay within rate limits
- Test your subscription form with real email addresses on the deployed site before launching — ad blockers and browser extensions in the development environment can sometimes interfere with form validation and network requests

## Use cases

### Subscriber Capture from Landing Page

A signup form on a Bolt.new marketing site that adds new subscribers to a specific Constant Contact list. Visitors enter their name and email, the form submits to a Next.js API route, and the route calls Constant Contact's POST /contacts endpoint to create the contact and add them to the designated list. Confirmation email sequences configured in Constant Contact fire automatically.

Prompt example:

```
Add a newsletter signup form to this landing page that subscribes users to a Constant Contact list. Create a form component with first name, last name, and email fields. On submit, call a Next.js API route at /api/constant-contact/subscribe that POSTs to https://api.cc.email/v3/contacts with CONSTANT_CONTACT_ACCESS_TOKEN from process.env. The contact should be added to the list ID stored in CONSTANT_CONTACT_LIST_ID. Map form fields to: first_name, last_name, email_address. Use the create_or_update action to handle duplicate emails. Show a success confirmation and error states. Use TypeScript and Tailwind CSS.
```

### Email Campaign Analytics Dashboard

An internal dashboard showing Constant Contact campaign performance metrics — open rates, click-through rates, bounce counts, and unsubscribes for recent campaigns. Marketing team members can view campaign results in a custom interface without logging into the Constant Contact platform, integrated alongside other business metrics in a single dashboard.

Prompt example:

```
Build a Constant Contact campaign analytics dashboard. Create a Next.js API route at /api/constant-contact/campaigns that fetches recent email campaigns from https://api.cc.email/v3/emails using CONSTANT_CONTACT_ACCESS_TOKEN. Also create /api/constant-contact/campaigns/[id]/stats that fetches campaign activity stats for a specific campaign ID. In React, display a list of recent campaigns with their name, sent date, and a summary of opens and clicks. On clicking a campaign, show a detail panel with open_rate, click_rate, bounces, and unsubscribes. Use recharts for a bar chart comparing open vs click rates across campaigns. Use Tailwind CSS for layout.
```

### Event Registration Sync to Constant Contact

A custom event registration form in Bolt that captures attendee details and automatically adds them to a Constant Contact list tagged with the event name. After the event, the marketing team can send follow-up emails directly from Constant Contact to all attendees using that list.

Prompt example:

```
Create an event registration form component that collects attendee name, email, company, and job title. On submission, call /api/constant-contact/contacts which should POST to Constant Contact's v3 contacts API (https://api.cc.email/v3/contacts) with CONSTANT_CONTACT_ACCESS_TOKEN. Add the contact to list CONSTANT_CONTACT_EVENT_LIST_ID and include a custom field 'event_name' with value 'Spring Conference 2026'. Handle the create_or_update action to avoid duplicates. Return a confirmation with the contact's ID. Show a loading state during submission and a thank-you message on success.
```

## Troubleshooting

### OAuth redirect returns 'invalid_redirect_uri' error from Constant Contact

Cause: The redirect URI in your authorization request does not exactly match the redirect URI registered in your Constant Contact developer app. Even a trailing slash difference or http vs https mismatch will cause this error.

Solution: Go to developer.constantcontact.com → My Applications → your app → edit the Redirect URI. Ensure it exactly matches the URL in your code, including protocol (https), domain, path, and no trailing slash. The redirect URI must also be a deployed URL — Bolt's WebContainer preview URL and localhost do not work as Constant Contact redirect URIs.

### POST to /contacts returns 400 Bad Request with 'email_address is required'

Cause: The email_address field in the Constant Contact v3 API is an object with 'address' and 'permission_to_send' sub-fields, not a plain string. Sending email as a top-level string field causes a validation error.

Solution: Ensure the contact payload wraps the email in the correct object structure. The email_address field must be an object: { "address": "user@example.com", "permission_to_send": "implicit" }.

```
// Correct contact payload structure for Constant Contact v3
const payload = {
  email_address: {
    address: email,          // email as nested 'address' field
    permission_to_send: 'implicit',  // required: 'implicit' or 'explicit'
  },
  first_name: firstName,
  last_name: lastName,
  list_memberships: [listId],
  update_source: 'Account',  // prevents duplicate errors
};
```

### Constant Contact API returns 401 Unauthorized after the integration worked previously

Cause: Constant Contact access tokens expire. When the token expires, all API calls return 401 until you refresh the token using the refresh token obtained during the original OAuth flow.

Solution: Use the refresh token to obtain a new access token. POST to https://authz.constantcontact.com/oauth2/default/v1/token with grant_type=refresh_token. Update CONSTANT_CONTACT_ACCESS_TOKEN in your .env and redeploy. For production apps, implement automatic token refresh logic that detects 401 responses and refreshes before retrying.

```
// Refresh an expired Constant Contact access token
async function refreshAccessToken(refreshToken: string) {
  const clientId = process.env.CONSTANT_CONTACT_CLIENT_ID!;
  const clientSecret = process.env.CONSTANT_CONTACT_CLIENT_SECRET!;

  const res = await fetch('https://authz.constantcontact.com/oauth2/default/v1/token', {
    method: 'POST',
    headers: {
      Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken }),
  });
  return res.json(); // contains new access_token and refresh_token
}
```

### Newsletter form submits successfully in the Bolt preview but contacts do not appear in Constant Contact

Cause: The API call is likely failing silently. Either the CONSTANT_CONTACT_ACCESS_TOKEN is missing or incorrect, or the list ID in CONSTANT_CONTACT_LIST_ID does not match any existing list in your account.

Solution: Open browser DevTools → Network tab and inspect the response from /api/constant-contact/subscribe. If it returns an error body, read the error_message field. Verify CONSTANT_CONTACT_ACCESS_TOKEN is set and starts with a valid token string. Find your list ID in Constant Contact → Contacts → Lists, click a list name, and copy the numeric ID from the URL.

## Frequently asked questions

### Does Constant Contact work with Bolt.new?

Yes. Constant Contact's v3 REST API uses standard HTTPS requests that work in Bolt's WebContainer via a Next.js API route. The main constraint is OAuth 2.0 authentication — the OAuth callback must be handled on your deployed site (Netlify or Bolt Cloud), not in the WebContainer preview, because OAuth requires a stable redirect URI. Once you have an access token, all subscriber management and campaign API calls work in both development and production.

### How do I connect Bolt.new to Constant Contact?

Register a developer app at developer.constantcontact.com to get a client ID and client secret. Deploy your Bolt app to Netlify or Bolt Cloud, register the deployed URL as the OAuth redirect URI, complete the OAuth authorization flow to get an access token, and store it in .env as CONSTANT_CONTACT_ACCESS_TOKEN. Then create Next.js API routes that call the Constant Contact v3 API with the token in the Authorization header.

### Can I test the Constant Contact integration in Bolt's development preview?

Partially. Outbound API calls to api.cc.email work in Bolt's WebContainer preview — you can test subscriber creation and campaign fetching in development. However, the OAuth 2.0 authorization flow (which requires a callback redirect from Constant Contact to your app) cannot be completed in the preview because the WebContainer uses dynamic URLs that cannot be registered as stable redirect URIs. Deploy to Netlify first to complete the OAuth setup.

### Do I need a paid Constant Contact plan to use the API?

Constant Contact does not offer a permanent free tier — only a free trial for new accounts. The trial provides full API access and is sufficient for building and testing your Bolt.new integration. After the trial, you need a paid plan (Email or Email Plus) to continue using the API in production. All paid plans include full API access with the same v3 endpoints.

### How do I add subscribers to a Constant Contact list from a Bolt form?

Create a Next.js API route that receives the form data and POSTs to https://api.cc.email/v3/contacts with an Authorization: Bearer {token} header. The request body must include email_address as an object with 'address' and 'permission_to_send' fields, plus a list_memberships array containing your list ID. Add update_source: 'Account' to enable upsert behavior and avoid duplicate contact errors.

### How do I deploy a Bolt.new Constant Contact app to Netlify?

Connect your Bolt project to Netlify through Settings → Applications. In Netlify's site settings under Environment Variables, add CONSTANT_CONTACT_CLIENT_ID, CONSTANT_CONTACT_CLIENT_SECRET, CONSTANT_CONTACT_ACCESS_TOKEN, and CONSTANT_CONTACT_LIST_ID. Register your Netlify URL as the redirect URI in your Constant Contact developer app. Trigger a redeploy after adding environment variables. Complete the OAuth flow on the live Netlify URL to obtain your access token.

---

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