A few years ago, my previous boss forwarded me an email that said:
"This daily audience email Mailchimp sends out sucks. Can you make it better?"
That question led me down a long and weird path of playing with the Mailchimp API and some Firebase functions to create a daily wrap-up email that examined each of our Mailchimp lists and their segments to track growth.
We looked at a few daily KPIs:
New readers in 24 hours
New unsubscribes in 24 hours
Total Subscribers
Readers subscribed to the daily newsletter
The daily newsletter email
Open rate
Click Rate
Subject Line
A link to the archived email
I also had some experience with CSVParser and wanted to give the editorial team a list of people who subscribed that day so they could reach out for an interview.
The Setup
Mailchimp Segments
The first part of the automation is setting up our reporting segments. Mailchimp's marketing API has access to Mailchimp's regular segmentation, so your advanced segments will NOT be available to the API. This is frustrating because advanced segments are usually more helpful, but we have to work with what's accessible.
You'll need to create a few regular segments:
New Readers in 24 Hours - This segment should include readers who joined in the last 24 hours and have an email marketing status of subscribed.
New Unsubscribes in 24 Hours - This segment should include readers who are email marketing unsubscribed and have had information change in the 24 hours.
Daily Subscribers - This segment should include readers who have a GDPR setting for daily subscribers (or whatever segmentation makes sense for your main newsletter list).
We'll be using the node mailchimp package for ease of use. There are a few gotchas and hiccups with the npm package that I'll call out.
Firebase Functions
We'll use Firebase's PubSub functions to send this email whenever you want. The beauty of pubsub is you can set it to run on a schedule—weekdays at 5 pm works perfectly since newsletters go out in the morning and subscribers have had time to open and click by late afternoon.
The Scheduled Function
The primary function runs every weekday at 5 pm Central. Since we send emails early in the morning (5-7 am), users have enough time to open and click them before we pull the data. The function orchestrates everything: pulling campaign data, retrieving segment counts, parsing new subscribers into a CSV file, and sending the email.
Getting Campaign Data
The first significant piece is a function that pulls today's newsletter performance data from Mailchimp. It takes a list ID as a parameter, which makes it reusable across multiple lists if you're running more than one newsletter.
Here's where Mailchimp's documentation gets annoying. The parameter casing is wrong in their docs. The documentation shows snake_case (before_send_time and since_send_time), but the actual API expects camelCase (beforeSendTime and sinceSendTime). I spent a ton of time debugging authentication errors before I figured out the docs were just wrong.
We hardcode the send time window (5-7 am) since our newsletters always go out during that window. This lets us query for campaigns sent today and assume we'll get only one result. We pull the campaign ID from that response, then use it to get the whole campaign report and the archive URL.
The function returns all the key metrics: unsubscribes, hard bounces, subject line, campaign title, open rate, click rate, and the archive URL so editors can click through to see which newsletter we're reporting on.
Pulling Segment Data
Next, we query each segment we created earlier to obtain counts. This is straightforward—Mailchimp's segment API returns a member_count field that tells us exactly how many people are in each segment.
We also pull the complete list of new subscribers from the "New Readers in 24 Hours" segment. For each subscriber, we collect their email address, first name, last name, company, title, and UTM parameters (source and medium). This gives the editorial team context on where subscribers came from.
The UTM data is beneficial—it shows which marketing channels are driving signups. If you're not tracking UTM parameters in Mailchimp, you should be.
Generating the CSV
We loop through the new subscribers and build an array of objects with their contact info. Then we pass that array to a CSV parser. The parser converts the array into a proper CSV file that we can attach to the email.
The CSV includes columns for email, first name, last name, company, title, UTM source, and UTM medium. This way, the editorial team can import it into a spreadsheet or CRM if they want to reach out to specific subscribers.
Sending the Email
The final piece is using nodemailer to send an email to Gmail with all the data. We set up an SMTP transport with Gmail credentials (you'll need to enable "less secure apps" or use an app-specific password), then construct an email containing all the metrics in plain text.
The email includes:
New subscribers count
Unsubscribes count
Total subscribers
Daily newsletter subscribers
Today's newsletter stats (subject line, open rate, click rate, with a link to the archived email)
CSV attachment of new subscribers
We also wrap the entire send operation in error handling so the function doesn't crash if Gmail has issues.
Scaling to Multiple Lists
Since the campaign data function takes a list ID parameter, you can efficiently run this for multiple newsletters. Just call the function multiple times with different list IDs, then use Promise.all() at the end to make sure all the reporting functions are complete before the pubsub function exits.
The Gotchas
Mailchimp API Casing: The documentation shows snake_case parameters, but the node package expects camelCase—this wasted hours of my life. Always check the actual npm package implementation, not just the API docs.
Advanced Segments: You cannot access advanced segments via the API. Only regular segments work. If you need complex segmentation logic, build it as a regular segment, not an advanced one.
Gmail Authentication: You'll encounter authentication issues with Nodemailer and Gmail. You have to allow "less secure apps" in your Google account settings, or better yet, generate an app-specific password. Never hardcode your Gmail password—use environment variables.
CSV Parsing: Make sure your CSV parser handles empty fields gracefully. Not every subscriber will have a company or title filled in, and you don't want the parser to choke on missing data.
Timing: We run this at 5 pm because newsletters go out in the morning. If you send newsletters at different times, adjust the schedule accordingly. You want to give subscribers enough time to open and click before pulling stats.
Conclusion
Deploy your code to Firebase and wait for the team to give you the congratulations you deserve. Our editorial team loved these emails so much that they looked forward to them every day. It also really helped that we provided a CSV of new subscribers so that the editorial team could review potential interviewees.
The best part? Zero manual work. Every day at 5 pm, everyone on the team gets complete transparency on newsletter performance without logging into Mailchimp.
Want the complete code?
This post covered the architecture and approach, but the actual implementation has nuance—handling edge cases in the Mailchimp API, proper CSV formatting, Gmail authentication setup, and all the parameter casing issues I figured out the hard way.
Sign up for the Automation Almanac newsletter and get the complete working code sent to your inbox. You'll get the full Firebase function, segment queries, CSV parsing logic, and email formatting—everything you need to deploy this today.
