Skip to content

Mailchimp

Paid add-on. Every consented submission subscribes or updates a member of a Mailchimp audience.

This document describes what the module guarantees and what it refuses. The user guide gives the short version, under “Subscribing to a Mailchimp audience”.

That is the difference from HubSpot and ActiveCampaign, and it is not an arbitrary hardening.

Those two are CRMs: the record there is an act of administration, distinct from the mailing list. The contact therefore always goes out — somebody wrote to the site — and only the subscription waits for agreement.

Mailchimp is the mailing list. Putting somebody on it is subscribing them: there is no administrative record to file first. A module that sent “only the member” without consent would be a silent export of contacts to a sending platform.

Three things are therefore required on the same footing: an audience, an address field, and a consent field. Until all three are set, no submission goes out, and the panel says so.

With no box ticked by the person, nothing is even scheduled: there is no queued row, no task, no trace. There is nothing to send.

The core’s Consent field records {accepted, text, at}: the exact wording accepted, and the date. Only that field will do — an ordinary checkbox records only a “yes”, and a “yes” without the wording it approved demonstrates nothing. For a mailing-list subscription, that is precisely what you will be asked to produce.

That value has two shapes. In memory, just after the submission, it is an array. Read back from the database, it is a JSON string: the repository serialises composite values.

Three modules read accepted after checking that the value was an array, and otherwise fell back on “a non-empty value means yes”. On the read-back shape, {"accepted":false,…} is a non-empty string — a refusal read as an agreement. Two of those modules only read consent at submission time and were not affected; this one re-reads it at send time, and was.

The reading is now shared by all three, in a single place that knows both shapes. On this question, unknown means no.

pending by default: double opt-in

pending has Mailchimp write to the person, who confirms by clicking. That is the default value and the one the screen recommends.

subscribed skips that confirmation. It is not forbidden — the module records precisely the proof that would allow it — but it is for the data controller to answer for, and the screen writes it out before letting you choose: without confirmation, nobody checks the address, a typo subscribes a stranger, and the consent you hold names somebody else.

The connection screen shows the number of members awaiting confirmation. That is the figure you come looking for when you doubt double opt-in: a number that never goes down says the confirmation emails are not arriving — an outage you would not otherwise notice, because everything looks fine from this side.

status_if_new, not status

That is the detail that stops the module doing harm.

status would impose the status on every send: somebody who had unsubscribed would find themselves resubscribed on the next submission of a form they once filled in — or by a simple click on “Resend” in the admin.

status_if_new only applies at creation. An existing member keeps the status they chose, and the module confines itself to updating their fields.

Mailchimp refuses to resubscribe whoever unsubscribed

Somebody unsubscribed, bounced or having reported abuse is answered 400 with a message saying so. Only they can come back, from a Mailchimp form.

That refusal is definitive: replaying it three times would not get round it, it would insist. The submission’s screen distinguishes it from a settings error and explains it — and offers no resend button there, because there is nothing to resend.

The address fingerprint is never kept

Mailchimp designates a member by the MD5 of their lowercased address. That is not the module’s choice, it is the API — and it is not an innocuous identifier: given an address, the fingerprint is verified in a second. It is an address in disguise.

It is therefore computed at the moment of the call and never written: not in the tracking table, nor in the delivery log, where the address called is reduced to lists/members — without the fingerprint, and without the audience’s id.

Erasing a submission takes its tracking row and its log with it. But a kept fingerprint would have survived in backups and exports, and would have read there as an innocuous technical datum when it designates a person.

What is kept instead is the web_id, the numeric identifier Mailchimp returns with the member: it opens their record and says nothing about anybody.

Merge fields belong to the audience

Not to the account. FNAME exists almost everywhere, MMERGE7 nowhere else.

Offering them from the whole account would lead to mapping a tag absent from the audience targeted — and an unknown tag has the whole subscription refused, not just the field. The screen therefore reads them for the chosen audience, and the setting bounds the tags to Mailchimp’s format: uppercase, ten characters at most.

EMAIL cannot be mapped: the address has its own setting, and mapping it a second time would send two of them, one of which does not serve to designate the member.

An empty value is not sent: a merge field sent empty erases the member’s, and a second submission with no first name would delete the one the first had put there.

The token does not expire, and there is nothing to refresh

Mailchimp returns expires_in: 0 and no refresh token. That runs counter to the other integrations’ habit: there is no cycle to keep up, and in exchange a secret that is worth as much as long as nobody revokes it.

It is therefore encrypted at rest, with a key of this integration’s own. And disconnecting revokes it explicitly at Mailchimp’s — erasing it here would not be enough. If that revocation fails, the screen asks you to go and remove the application on the Mailchimp side, because otherwise the access stays alive at a third party’s, with no end date.

The API’s address arrives over the network

login.mailchimp.com carries the authorisation; the API lives on the account’s data centre — us19.api.mailchimp.com — obtained by a second call after the exchange, on an endpoint provided for that.

That address therefore comes from a network response, and it is the one we will call with a token. It is checked against the .api.mailchimp.com suffix and the https scheme — the leading dot being what separates a Mailchimp data centre from an attacker-api.mailchimp.com.

An exchange that returned a token with no acceptable root is treated as a failure: better to refuse the connection than to record one that points nowhere.

What is not attempted, and what is retried

Nothing is scheduled without consent, without an audience, without an address field, without a connection, or if the form’s condition is not met. A submission marked as spam does not go out. A malformed address is a definitive refusal written before any call.

Definitive refusals are not replayed: audience gone, unknown tag, resubscription refused. 429 and 5xx are, at 1, 5 then 30 minutes.

A failed tagging does not take back an acquired subscription. The person is subscribed, and the reason is written beside it.

A Mailchimp outage never blocks the submission. It is recorded, confirmed to the visitor and notified by email before this module is called on.

Resending resubscribes nobody

The resend button replays the PUT with status_if_new: an existing member keeps the status they chose. A resend therefore updates their fields, and can neither resubscribe them after an unsubscribe nor pull them out of awaiting confirmation.

It does not appear on a submission without consent, since there is no row.

Schema

slf_mailchimp_members: one row per subscribed submission — and none for the others — with the audience, the web_id, the status Mailchimp returned, the tagging, the state, the reason for the last problem and the number of attempts.

There is no consent column: it could only have had one possible value, and carrying it would have suggested there exists a state in which it is zero.

The connection lives in an option: the client id and the API root in clear — they are not secrets, and the root is the first thing to check when nothing is going out — the token and the secret encrypted.

What V1 does not do

No campaign, no segment, no commerce, no reading of the audience, no member deletion, no reverse synchronisation.

And no handling of unsubscribes: unsubscribing belongs to Mailchimp and to the person. A form must not be able to undo it — nor, for that matter, to perform it on their behalf.