Skip to content

Intercom is a terrible Mailchimp. So is Mailchimp.

Jeff Schnitzer edited this page Sep 11, 2023 · 11 revisions

Sept 08, 2023

TL;DR A journey of discovery where I abandon Mailchimp for Intercom, get surprised by a crazy bill, then scratch-build something better (for us) than either. Rough HOWTO included.

Mailchimp -> Intercom

Like most SaaS companies, OrbitKit has a mailing list for our newsletter. It's not a large list (under 10k members) and we send newsletters infrequently (few times a year). When we set up the list, we used Mailchimp - mostly without thinking about it. It did the job, but paying almost a thousand bucks a year ($80/mo) for something we barely use started to annoy me. The clunky 2000s-era web interface didn't help with the annoyance factor.

We also use Intercom.io to handle our support load. I don't have a lot to complain about Intercom, and prior to this experience I would have recommended them without hesitation. We are not Intercom power users - they offer a gigantic pile of features, but all we really use them for is basic customer support and maintaining our documentation. For our two seats we pay $87/mo. Feels like a bargain.

A couple of months ago it occurred to me - hey, Intercom sends mail! I wonder if it can replace Mailchimp? Getting a little more familiar with the documentation, sure enough - there's even builtin support for the concept of Newsletters with optin/optout flags on the Contact record. This should be great!

Exporting our recipient list from Mailchimp and merging it (with their subscribe/unsubscribe status) into Intercom via their API was straightforward. There seemed to be only one catch - to theme the email with our company colors, I needed to add the "Messages Pro" upgrade to our Intercom account. This was going to increase our bill by $95:

Screenshot of Intercom's upgrade dialog

$95/mo is more than Mailchimp's $80/mo, but we're invested into Intercom and it is nice to cull our menagerie of third party service dependencies. Mailchimp's UI is annoying, and I'd rather give money to Intercom. Also I already imported the contact list. Okay!

I have to say that Intercom's themed-email builder is nicer than Mailchimp's. Not that we need a lot of theming, but it feels like a refreshingly modern UI. I whipped up our latest newsletter and sent it off. Everything worked great!

I smugly cancelled our Mailchimp subscription. When I clicked the button, Mailchimp presented me with two choices:

  1. Completely delete the account and wipe all data
  2. Pay $12 and suspend the account (preserving the data for easy resume later)

I don't know how they arrived at the $12 number but it pissed me off. I picked #1. Not. Going. Back.

This was a mistake.

Readers more familiar with Intercom probably already know what's coming. Some time later, at the end of our Intercom billing cycle, we received an invoice for $700.57.

Seven hundred dollars.

Combing through the bill, I discovered that different features of Intercom have different modalities for billing:

  • "Inbox Essential" (the support system) is charged per seat.
  • "Articles Essential" (the knowledge base) is a flat monthly charge.
  • "Messages Pro" is charged per active contact.

Guess what makes a contact count as active? Sending them email. Fuck.

Sure, this is my fault for not understanding their billing model. But in my defense, their billing model is Byzantine with lots of upsells and pricing modalities. And the upcharge screen is incredibly misleading - it shows the upcharge for all the users we're currently not emailing.

Obviously this wasn't going to work, so I cancelled "Messages Pro" and brought our bill back to $87/mo. But I still needed to send newsletters. Too bad I deleted our Mailchimp account...

Intercom -> ?

Intercom is cost prohibitive and I'm too proud to go crawling back to Mailchimp. We write software, why do we need a third party service?

Here's what Mailchimp/Intercom was doing for us, newsletter-wise:

  • Maintain the subscriber list
    • Intercom's API lets us interrogate and twiddle a contact's subscribe/unsubscribe status. It can remain the master database.
  • Send email
    • Our application already sends email.
  • Make pretty emails
    • We write HTML, and our newsletters are waaay simpler than our web pages.
  • Handle unsubscribe links
    • Easy enough to make a link that twiddles the Intercom database.
  • Track delivery and open rates
    • We never actually look at that data. We can live without it.

This doesn't sound like a hard problem, why not build it from scratch? With plenty of time until the next newsletter, it was a fun backburner project.

Jeffchimp

The UI is gloriously simple: A single page in our admin UI with text fields for subject and body. Plus buttons for "send test" and "send to everyone". Also a link to the google task queue that handles mail (more on that later).

Send newsletter UI

What email format?

Mailchimp and Intercom both send multipart/related emails with text/html and text/plain parts. Exactly 0% of our audience is reading email with pine or mutt, so sticking with html-only keeps everything simpler. We can edit an HTML file, look at it in a browser, and paste it into the "body" field of our send newsletter admin page.

How to handle unsubscribe?

When sending each individual message, our code finds the </body> tag and inserts an unsubscribe link. The link contains a JWT with the user's Intercom contact id.

The page at the unsubscribe link verifies the token, looks up the contact in Intercom, and unsubscribes them from the newsletter using Intercom's API. There's nothing else you can do with the token (except re-subscribe if you clicked it by accident).

Unsubscribe UI

CSS Styling

HTML email readers are less sophisticated than web browsers and only accept a limited subset of HTML and CSS. You don't have to worry about this using email templates from Mailchimp or Intercom, but you do if you're hand-writing HTML.

Turns out, it's not that hard. Most of the advice on the internet is very old and email readers have advanced. You don't need to inline styles; you can use flexbox. The main rules today are:

  • No javascript.
  • No external stylesheets.
  • Design for a narrow window. I used 600px.

If you want fancy, you can rely on something like MJML. We don't need fancy, so I just put some CSS in a <style> block at the top.

Images in the email?

I didn't want a complex workflow that involves uploading images to a bucket and copying links into the email. So, how about using data: urls for image sources?

<img alt="OrbitKit Logo" src="data:image/png;base64,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"/>

Here's a simple (OSX) shell script that you can run on any file and get a data: url:

TYPE=$(file -b --mime-type "$1")
ENC=$(base64 -i "$1")
echo -n "data:$TYPE;base64,$ENC"

Pipe it to pbcopy and paste it into your img src:

$ dataurl.sh my-cool-logo.png | pbcopy

The whole email body, including images, remains a single block of HTML text and can be pasted into our "send newsletter" console.

Wait, data: urls for images?

It turns out this doesn't actually work. Gmail (and possibly other) email clients won't render images with data: sources. Oops.

However, email readers will render embedded images in html email. The email content type must be multipart/related and the whole body looks something like this (many important headers hidden for clarity):

Subject: This has a cool embedded image
Content-Type: multipart/related; boundary="----myboundary----"

----myboundary----
Content-Type: text/html; charset=utf-8

<html>
  <body>
    <img src="cid:theImage"/>
  </body>
</html>
----myboundary----
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-ID: theImage

THEIMAGEDATA

That is a huge pain in the ass to assemble by hand. However, it's pretty straightforward to transmogrify html with data: urls into this mime structure. Here's some Java code.

So from the sender's perspective, the newsletter email (including images) is still a single block of HTML text. Yeay.

Who gets the newsletter?

Intercom's API sometimes feels a little primitive. There's no way to query for contacts who are subscribed to a newsletter. But you can query for all the contacts and get their subscription status in the result. Might be annoying with millions of contacts, but for tens of thousands it's fine.

Reliability

I feel compelled to give a shout-out to Google App Engine here - specifically, Cloud Tasks. Iterative processing with large numbers is somewhat hazardous - you don't want to send out half your newsletter and abort with an error. Task queues make it easy.

The flow is:

  1. Pause the newsletter queue
  2. Iterate through the contacts; for each subscriber enqueue a SendNewsletterTask
  3. When satisfied that the fanout completed without error, manually unpause the queue and let it rip

The human is the transaction monitor.

Deliverability

I ran our newsletter through https://www.mail-tester.com/. After adding an MX record and ensuring that every <img> has an alt tag, our score was 9.9/10. The MX record issue was affecting "normal" email sent by our application, so this was a good exercise.

The last 0.1 is for having text/html with no text/plain alternative. We could automatically render the html to text, but it seems pretty minor.

[Thanks to mceachen on HN for this suggestion]

You've Got Mail

We sent out our first newsletter with the new infrastructure, and it worked great. Total monthly cost of this service: $0.

The new newsletter is actually better than the old in one interesting way. Since images are being sent inline instead of as external links, email readers just display them instead of the "Show images?" prompt.

Closing Thoughts

There was a bit more code than I originally expected (mostly the image handling) but it summed to a couple solid days of work. $80/mo is not a lot but it was fun work and it adds up over time (~$4k already, not counting the Intercom mistake).

We aren't tracking open rates. To do that, we'd have to switch to external images, embed tokens in (one of) the URLs, and track persistent state when each gets fetched. It's not hard but not currently worth the effort. Maybe someday.

Someone suggested that spam filters might treat HTML with embedded images worse than HTML with external images. Maybe? I could just as easily believe the opposite of that to be true. I'd love to hear more.

This experience has left me fairly disappointed with Intercom's product. Yes, I shot myself in the foot, but it was a particularly egregious footgun. Their billing system is too complex; I don't want to have to worry that turning on a minor feature will suddenly 10X my bill. They tried to make billing transparent by showing you the future cost, but for this particular feature, it's a bald-faced lie. And why isn't this feature cost competitive with Mailchimp? It's definitely not worth 5X or 10X what Mailchimp costs. Not even 2X.

I have no intention of replacing Intercom over this, but my recommendation has gone from "just use Intercom" to "maybe use Intercom, but survey the market and evaluate alternatives first".

I hope there's enough of a breadcrumb trail here for anyone else to repeat the same process. If you have any trouble, reach out!

Discuss this on Hacker News

Me

Hi, my name is Jeff. I've been the CTO of companies large and small, but mostly I like to write code.

If you want to be notified of blog updates, follow the repository.

Clone this wiki locally