Connect FlutterFlow to Mailchimp by routing Mailchimp Marketing API v3 calls through a Firebase Cloud Function or Supabase Edge Function. The most important detail: your Mailchimp API key's suffix determines the data-center prefix in the base URL — key ending in '-us14' means base URL https://us14.api.mailchimp.com/3.0. The proxy holds the API key as Basic Auth and handles all subscriber operations so the credential never ships in the Flutter binary.
| Fact | Value |
|---|---|
| Tool | Mailchimp |
| Category | Marketing |
| Method | FlutterFlow API Call |
| Difficulty | Intermediate |
| Time required | 60 minutes |
| Last updated | July 2026 |
Add App Users to Mailchimp Lists from FlutterFlow
The most common FlutterFlow-to-Mailchimp use case is 'subscribe this user to my newsletter when they sign up'. It sounds simple — and it is, once you clear the one unique hurdle: Mailchimp's data-center URL. Unlike most APIs that use a single base URL, Mailchimp routes requests to data-center-specific servers. Your API key ends with a suffix like '-us14' or '-us6'. That suffix is the subdomain you must use: https://us14.api.mailchimp.com/3.0 or https://us6.api.mailchimp.com/3.0. If you use the wrong data center — even with a completely valid API key — you get a 404 or a misleading authentication error. This is the single most common reason Mailchimp integrations fail silently. Mailchimp's Free plan supports a limited number of contacts and monthly sends; paid tiers scale by audience size — check current pricing on mailchimp.com for your volume.
Authentication in the Mailchimp API is HTTP Basic Auth. The username can be any string (Mailchimp ignores it); the password is your API key. In a standard API request this looks like `Authorization: Basic base64(anystring:YOUR_API_KEY)`. This credential must never appear in a FlutterFlow API Call — the compiled Flutter binary would expose it. A Firebase Cloud Function or Supabase Edge Function holds the API key in server environment variables and constructs the correct Basic Auth header before forwarding to Mailchimp.
The core subscriber operation is a POST to /lists/{list_id}/members with a JSON body containing the email address and a status field. The status can be 'subscribed' (immediate opt-in) or 'pending' (double opt-in confirmation email sent). If your Mailchimp audience has double opt-in enabled and you send status: 'subscribed', the API rejects the request. Always check your audience settings in Mailchimp before deciding which status to use. For bulk imports — adding hundreds of app users at once — use the batch endpoint on the server side rather than looping individual member calls from the Flutter client, which would slam the rate limit.
Integration method
Mailchimp's Marketing API v3 uses HTTP Basic Auth where the username is any string and the password is your API key. The API key also encodes your data center — the suffix after the last hyphen (e.g., 'us14') becomes the subdomain of your base URL. Both the key and the correct data-center URL must live in a backend proxy because the API key in a FlutterFlow API Call header ships inside the compiled Flutter app. FlutterFlow calls your Firebase Cloud Function or Supabase Edge Function proxy, which injects the correct Basic Auth header and data-center URL before forwarding the request to Mailchimp.
Prerequisites
- A Mailchimp account — the Free plan is sufficient for basic subscriber management
- A Mailchimp API key (Account → Extras → API keys → Create A Key) with the data-center suffix noted (e.g., the '-us14' part at the end of the key)
- At least one Mailchimp Audience (list) with its List ID copied (Audience → Settings → Audience name and defaults → Audience ID)
- A Firebase project with Cloud Functions on Blaze plan or a Supabase project with Edge Functions enabled
- A FlutterFlow project with a sign-up or email capture form built
Step-by-step guide
Get your Mailchimp API key and note the data-center suffix
In Mailchimp, click your account avatar (top right) → Account & billing → Extras → API keys. If you have no existing keys, click Create A Key and give it a descriptive name like 'FlutterFlow Integration'. Copy the full API key — it looks like a32b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8-us14. The critical part is the two-part suffix at the end: the characters after the last hyphen ('us14' in this example) are your data center identifier. This determines your API base URL. Write down two things: (1) the complete API key — keep it safe, this is the credential; (2) the data center suffix — 'us14', 'us6', 'us2', etc. Your Mailchimp API base URL is https://{dc}.api.mailchimp.com/3.0 where {dc} is your suffix. For us14 that becomes https://us14.api.mailchimp.com/3.0. Now also get your Audience List ID: in Mailchimp go to Audience → All Contacts → Settings → Audience name and defaults. The Audience ID is a string like 'abc123def4' displayed under the audience name. Copy it — you will use it in the API endpoint path when subscribing members. You can have multiple audiences; each has its own List ID.
Pro tip: Your data center can also be confirmed by logging in at https://login.mailchimp.com — after login, the URL in your browser changes to https://usXX.admin.mailchimp.com where XX is your data center number.
Expected result: You have your full API key, the data-center identifier (e.g., 'us14'), and the Audience List ID — all three needed for the proxy configuration.
Deploy a Firebase Cloud Function proxy with Basic Auth and the correct data-center URL
Create a Firebase Cloud Function that handles Mailchimp subscriber operations. The function must construct the Basic Auth header by base64-encoding 'anystring:YOUR_API_KEY' and prefix the Mailchimp URL with the correct data-center subdomain — both values come from environment variables. The function accepts a POST from FlutterFlow with an operation type and subscriber email, then routes the request to the appropriate Mailchimp endpoint. Important CORS note: include Access-Control-Allow-Origin headers because FlutterFlow web builds run in a browser. For the subscribe operation, POST to /lists/{list_id}/members with the body { email_address, status }. For campaigns, GET /campaigns with query parameters for pagination and sorting. The status value is critical — if your Mailchimp audience has double opt-in enabled (the default for most new audiences), you must use 'pending' not 'subscribed'. Sending 'subscribed' to a double-opt-in audience returns a 400 error. Deploy with the Firebase CLI from your local machine and set the MAILCHIMP_API_KEY and MAILCHIMP_DC (the data-center like 'us14') as environment variables using Firebase Secret Manager or config.
1// functions/index.js (Firebase Cloud Functions)2const functions = require('firebase-functions');3const fetch = require('node-fetch');45exports.mailchimpProxy = functions.https.onRequest(async (req, res) => {6 res.set('Access-Control-Allow-Origin', '*');7 res.set('Access-Control-Allow-Methods', 'POST, OPTIONS');8 res.set('Access-Control-Allow-Headers', 'Content-Type');9 if (req.method === 'OPTIONS') { res.status(204).send(''); return; }1011 const API_KEY = process.env.MAILCHIMP_API_KEY;12 const DC = process.env.MAILCHIMP_DC; // e.g. 'us14'13 const BASE_URL = `https://${DC}.api.mailchimp.com/3.0`;14 const AUTH = 'Basic ' + Buffer.from(`anystring:${API_KEY}`).toString('base64');1516 const { operation, list_id, email, status, merge_fields } = req.body;1718 const headers = {19 'Authorization': AUTH,20 'Content-Type': 'application/json'21 };2223 let endpoint, method, body;2425 switch (operation) {26 case 'subscribe':27 endpoint = `/lists/${list_id}/members`;28 method = 'POST';29 body = JSON.stringify({30 email_address: email,31 status: status || 'subscribed',32 merge_fields: merge_fields || {}33 });34 break;35 case 'list_campaigns':36 endpoint = '/campaigns?count=10&sort_field=send_time&sort_dir=DESC';37 method = 'GET';38 break;39 default:40 res.status(400).json({ error: 'Unknown operation' });41 return;42 }4344 const mcRes = await fetch(`${BASE_URL}${endpoint}`, { method, headers, body });45 const data = await mcRes.json();46 res.status(mcRes.status).json(data);47});48Pro tip: Set both variables explicitly: firebase functions:config:set mailchimp.key=YOUR_KEY mailchimp.dc=us14 — then read them as process.env.MAILCHIMP_API_KEY (update the variable name to match your config key format).
Expected result: The Cloud Function is deployed and returns 200 with a new Mailchimp member object when called with a subscribe operation, a valid list_id, and an email address.
Create a FlutterFlow API Group and wire the subscribe action
In FlutterFlow, open API Calls in the left nav and click + Add → Create API Group. Name it MailchimpProxy. Paste your Firebase Cloud Function HTTPS trigger URL as the Base URL. Add no Authorization headers — the proxy handles auth. Create an API Call inside the group called subscribeUser. Set method to POST, leave the endpoint path blank. In the Body tab select JSON. Add these body variables: operation (String, default value 'subscribe'), list_id (String — your Mailchimp Audience List ID, or a variable if you have multiple audiences), email (String), status (String, default 'subscribed'), and merge_fields (String — a JSON object for custom merge tags, default '{}'). Your body template: { "operation": "{{operation}}", "list_id": "{{list_id}}", "email": "{{email}}", "status": "{{status}}", "merge_fields": {{merge_fields}} }. In Response & Test, fill in a real email address and your list_id, and click Test. A successful subscribe returns 200 with an id field — the unique hash of the subscriber. Now go to your sign-up or email capture screen. In the Action Flow Editor, after the form is submitted, add a Backend/API Call action selecting subscribeUser. Pass the user's email from the form field, the list_id as a constant (you can store it in App Values), and status as 'subscribed' or 'pending' depending on your audience's opt-in settings. Add a Snackbar action after the API Call: show 'Subscribed successfully!' on 200 and 'Subscription failed — please try again' on any other status.
1{2 "operation": "{{operation}}",3 "list_id": "{{list_id}}",4 "email": "{{email}}",5 "status": "{{status}}",6 "merge_fields": {{merge_fields}}7}Pro tip: Store your Mailchimp List ID in App Values (App Settings → App Values) in FlutterFlow so it is easy to find and update — App Values are not secrets since they compile into the binary, but list IDs are not sensitive.
Expected result: Submitting the form in the FlutterFlow app creates a new Mailchimp subscriber visible in your audience within seconds.
Read campaign stats into a dashboard Data Type
Create a second API Call in the MailchimpProxy group named listCampaigns. Set method to POST, leave the endpoint blank. Body: { "operation": "list_campaigns" }. In Response & Test, click Test. The response includes a campaigns array with objects containing each campaign's id, settings.subject_line, send_time, report_summary.open_rate, report_summary.click_rate, and status. Click Generate from Response to create JSON Paths. Create a Data Type called MailchimpCampaign with fields: campaignId (String), subjectLine (String), sendTime (String), openRate (Double), clickRate (Double), status (String). Map the JSON Paths: $.campaigns[*].id, $.campaigns[*].settings.subject_line, $.campaigns[*].send_time, $.campaigns[*].report_summary.open_rate, $.campaigns[*].report_summary.click_rate. Open rates and click rates come as decimals (0.245 = 24.5%) — use FlutterFlow's number formatting to display them as percentages by multiplying by 100. Build a Campaign Stats screen with a ListView bound to the listCampaigns Backend Query. In each list item, show the subject line as the title, the send date formatted to a readable string, and the open rate and click rate as percentage chips. This gives a founder or marketer a quick email performance overview from their FlutterFlow app without opening the Mailchimp dashboard.
Pro tip: Mailchimp returns report_summary only for campaigns with status 'sent' — draft or scheduled campaigns will have null report_summary fields. Add null safety checks in your FlutterFlow widget bindings or filter the API response to only show sent campaigns.
Expected result: The Campaign Stats screen shows the last 10 sent campaigns with subject lines, send dates, open rates, and click rates loaded from the Mailchimp proxy.
Handle double opt-in and bulk subscriber imports safely
Two edge cases require specific handling. First, double opt-in: if your Mailchimp audience requires confirmation emails (check Audience → Settings → Audience name and defaults → 'Enable double opt-in'), you must send status: 'pending' in the subscribe body, not 'subscribed'. Sending 'subscribed' to a double-opt-in audience returns a 400 error with 'Member in compliance state' or 'Invalid resource'. In FlutterFlow, let users choose or configure the default status per audience in your proxy. Second, bulk imports: if you want to sync a batch of existing app users to Mailchimp — for example, after adding Mailchimp to an app that already has 500 registered users — do not loop the subscribeUser API Call from FlutterFlow's device. Firing 500 API calls from the client app would exhaust Mailchimp's ~10-simultaneous-connection limit immediately. Instead, store the emails to sync in Firestore or Supabase, then trigger a Cloud Function that calls the Mailchimp batch endpoint (POST /batches) with an operations array. The batch endpoint accepts up to 500 operations per call and processes them asynchronously. If you need help building the bulk sync pipeline or multi-audience management for your app, RapidDev's team builds FlutterFlow integrations like this every week — reach out for a free scoping call at rapidevelopers.com/contact.
1// Bulk import via Mailchimp batch endpoint (Cloud Function, not FlutterFlow)2async function batchSubscribe(emails, listId, auth, baseUrl) {3 const operations = emails.map(email => ({4 method: 'POST',5 path: `/lists/${listId}/members`,6 body: JSON.stringify({7 email_address: email,8 status: 'subscribed'9 })10 }));1112 const res = await fetch(`${baseUrl}/batches`, {13 method: 'POST',14 headers: { Authorization: auth, 'Content-Type': 'application/json' },15 body: JSON.stringify({ operations })16 });17 return res.json();18}19// Call in batches of up to 500:20const BATCH_SIZE = 500;21for (let i = 0; i < allEmails.length; i += BATCH_SIZE) {22 const batch = allEmails.slice(i, i + BATCH_SIZE);23 await batchSubscribe(batch, LIST_ID, AUTH, BASE_URL);24}25Pro tip: After calling the Mailchimp batch endpoint, poll the returned batch ID at GET /batches/{batch_id} to check when processing is complete — batch jobs run asynchronously and may take several minutes for large imports.
Expected result: Double opt-in audiences receive 'pending' status subscriptions that trigger Mailchimp confirmation emails, and bulk imports process via the batch endpoint without hitting the connection limit.
Common use cases
Newsletter sign-up on app registration
A content app adds a 'Subscribe to weekly tips' checkbox on the registration screen. When checked, a FlutterFlow action calls the proxy after account creation to add the new user's email to the main Mailchimp audience with 'subscribed' status, enrolling them in the welcome sequence automatically.
After user registration, if the newsletter checkbox is checked, call the Mailchimp proxy to add their email to the 'Main Newsletter' audience list with subscribed status.
Copy this prompt to try it in FlutterFlow
Lead magnet delivery with email capture
A FlutterFlow app offers a free resource download. Before showing the download link, the user enters their email and clicks 'Get Free Guide'. A FlutterFlow action calls the proxy to add them to a 'Lead Magnet' Mailchimp audience with a custom merge tag for the resource they requested, triggering a Mailchimp automation that delivers the guide.
Build a modal where the user enters their email to unlock a free guide download. On submit, add them to the Mailchimp 'Lead Magnets' audience with a RESOURCE merge tag set to the guide name.
Copy this prompt to try it in FlutterFlow
In-app campaign stats dashboard for a small business owner
A restaurant owner uses a FlutterFlow app to see Mailchimp campaign open rates and click rates for their last three email newsletters, displayed in simple metric cards. The data comes from the Mailchimp Campaigns API through the proxy.
Create a screen showing the last 5 Mailchimp campaigns with subject line, send date, open rate, and click rate, loaded from a Mailchimp proxy.
Copy this prompt to try it in FlutterFlow
Troubleshooting
Mailchimp returns 404 or 'Resource Not Found' despite a valid API key
Cause: The data-center prefix in the URL does not match the data center associated with your API key. For example, using https://us1.api.mailchimp.com/3.0 when your key ends in '-us14' routes to the wrong server.
Solution: Check the last segment of your API key after the final hyphen — for 'abc123-us14' the data center is 'us14'. The correct base URL is https://us14.api.mailchimp.com/3.0. Update the MAILCHIMP_DC environment variable in your Cloud Function, redeploy, and retry. You can also verify by visiting https://usXX.admin.mailchimp.com after logging in — the number in the URL is your data center.
Mailchimp returns 400 with 'Member in compliance state' or 'Invalid resource' when subscribing
Cause: Your Mailchimp audience has double opt-in enabled, but the subscribe request is using status: 'subscribed' instead of status: 'pending'. Sending 'subscribed' to a double-opt-in audience bypasses confirmation and is blocked by Mailchimp's compliance rules.
Solution: Change the status value in your proxy call to 'pending'. The user will receive a Mailchimp confirmation email and will be added as 'subscribed' only after clicking the confirmation link. Check your audience's opt-in setting in Mailchimp under Audience → Settings → Audience name and defaults → 'Enable double opt-in'.
Existing subscriber returns 400 with 'Member Exists'
Cause: The email address is already in the Mailchimp audience. Mailchimp does not silently ignore duplicate subscribes — it returns an error.
Solution: Add error handling in your FlutterFlow action flow that checks for a 400 response and inspects the error title. If it contains 'Member Exists', show a friendly message like 'You are already subscribed!' instead of a generic error. You can also use PUT to /lists/{list_id}/members/{subscriber_hash} to update an existing subscriber's properties rather than erroring on duplicate.
FlutterFlow web build shows CORS error when the API Group tries to call the proxy
Cause: The proxy Cloud Function is missing the Access-Control-Allow-Origin response header, or the FlutterFlow API Group Base URL is pointing directly to api.mailchimp.com instead of the Cloud Function URL.
Solution: Confirm the Base URL in the FlutterFlow API Group points to your Cloud Function URL. Check that the function includes res.set('Access-Control-Allow-Origin', '*') on every code path that sends a response, including the OPTIONS preflight handler (which must return 204 with the CORS headers). Redeploy after changes.
Best practices
- Always derive the data-center prefix from your API key's suffix before configuring the proxy — a wrong data center causes silent failures that look identical to authentication errors.
- Never put the Mailchimp API key in a FlutterFlow API Call header or body — it compiles into the Flutter binary and can be extracted from the app package.
- Check your audience's double opt-in setting before choosing 'subscribed' vs 'pending' status — sending the wrong one causes a 400 error that is confusing to debug.
- Handle the 'Member Exists' 400 error gracefully in your FlutterFlow action flow so returning users see a helpful message rather than a generic failure.
- Use the batch endpoint for any sync of more than 10 subscribers — looping individual member calls from the Flutter client will hit connection limits and slow the app.
- Store the Mailchimp List ID in FlutterFlow's App Values constants so you can update it without a code change if you switch audiences.
- Use merge_fields (like FNAME, LNAME, or custom tags) when subscribing so Mailchimp automations can personalize email content immediately.
- For campaign stat displays, filter to status: 'sent' in the Mailchimp API response — draft and scheduled campaigns have null report_summary fields that will cause widget binding errors.
Alternatives
Klaviyo offers a cleaner public-key vs private-key model for in-app event tracking and has stronger e-commerce integrations — better if your app is a shopping or subscription product rather than a content newsletter.
HubSpot covers the full CRM including contacts, deals, and pipelines rather than just email lists — choose it if you need lead management and sales pipeline visibility in addition to email marketing.
SendGrid is optimized for transactional emails (welcome messages, password resets, order confirmations) triggered by individual user actions rather than list-based campaigns — the right choice for per-user email automation rather than bulk newsletters.
Frequently asked questions
Why does the Mailchimp base URL have a data-center prefix when most APIs don't?
Mailchimp routes API traffic to regional data centers to comply with data residency requirements and improve performance. Your account and all its subscribers live on a specific data center, and the API only accepts requests at that center's subdomain. The data center is encoded in your API key's suffix (the segment after the last hyphen) so you always have it available without a separate lookup.
What is the difference between 'subscribed' and 'pending' status in Mailchimp?
A 'subscribed' contact is immediately added to your active audience and will receive your next campaign. A 'pending' contact has been added to a confirmation queue — Mailchimp sends them a confirmation email and they only become 'subscribed' after clicking the confirmation link. Double opt-in (which requires 'pending') is the default for new Mailchimp audiences and is required in some countries for GDPR compliance. Single opt-in (which allows 'subscribed' directly) must be explicitly enabled in your audience settings.
Can I unsubscribe or delete a user from Mailchimp using this integration?
Yes — add an unsubscribe operation to your proxy that sends a PATCH request to /lists/{list_id}/members/{subscriber_hash} with the body { 'status': 'unsubscribed' }. The subscriber hash is the MD5 hash of the lowercase email address. You can also permanently archive a contact with DELETE /lists/{list_id}/members/{subscriber_hash}. Wire these to a FlutterFlow action on a settings screen where users can manage their email preferences.
Can I use custom merge tags when subscribing users?
Yes — include a merge_fields object in the subscribe body with your custom field names. Standard fields are FNAME, LNAME, PHONE. Custom merge tags are created in Mailchimp under Audience → Settings → Audience fields and *|MERGE|* tags. Use the merge tag name (the ALLCAPS abbreviation) as the key in the merge_fields object. For example: { 'FNAME': 'Jane', 'LNAME': 'Doe', 'PLAN': 'premium' }.
Is there a Mailchimp free plan limit I should know about before going live?
Mailchimp's Free plan limits change periodically — check the current limits at mailchimp.com/pricing before planning your integration. At the time this was written, the free tier covered a limited number of contacts and monthly email sends. Beyond those limits, sends are paused until you upgrade. Design your FlutterFlow subscriber flow with this in mind: consider a soft notification that warns the team when contact count approaches the free tier ceiling.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation