MailChimp Subscription with JavaScript

Written 9 months ago

Typically, one would use MailChimp’s embed forms or custom form using their builder. After submission, the form redirects the user to MailChimp’s confirmation page. Boo.

I wanted to create a MailChimp subscription without navigating away so that I can register a SalesForce lead.

With MailChimp’s API v3 you can add a subscription by sending a POST request to /3.0/lists/{list_id}/members. But that requires authorization using an API Key which is supposed to be secret, so that’s a no-go because I want it to be exclusively client-side.

Back to the embed forms I mentioned earlier. As with many things on the web, I ended up hacking it. I wrote a function mailchimpSubscribe that creates an invisible iframe with a form in it. It populates the form with input elements based on the parameters given to the function (email, name, last name, …) and submits the form. It returns a promise which resolves when MailChimp has received the request.

/**
 * Subscribes an email address to a MailChimp mailing list.
 * 
 * @param {string} url URL to POST to, you can get it by clicking on "Embedded forms"
 * @param {array} body array of objects with keys `key` and `value`
 * @return {promise}
 * @example
 * mailchimpSubscribe('https://***.***.list-manage.com/subscribe/post?u=***********************&id=*********', [
 *   { key: 'EMAIL', value: 'omarchehab98@gmail.com' },
 *   { key: 'NAME', value: 'Omar Chehab' },
 *   { key: 'LNAME', value: 'Chehab' },
 *   { key: 'PREFIX', value: 'Mr.' },
 *   { key: 'b_***********************_*********', value: '' },
 * ])
 *     .then(console.log)
 */
function mailchimpSubscribe(url, body) {
  const hiddenIFrame = document.createElement('iframe');
  hiddenIFrame.style.display = 'none';

  const hiddenForm = document.createElement('form');
  hiddenForm.setAttribute("action", url);
  hiddenForm.setAttribute("method", "post");
 
  body.forEach(({ key, value }) => {
    const hiddenInput = document.createElement('input');
    hiddenInput.setAttribute('name', key);
    hiddenInput.setAttribute('value', value);
    hiddenInput.setAttribute('type', 'hidden');
    hiddenForm.appendChild(hiddenInput);
  });

  document.body.appendChild(hiddenIFrame);
  hiddenIFrame.contentDocument.body.appendChild(hiddenForm);

  return new Promise((resolve) => {
    hiddenIFrame.addEventListener('load', resolve);
    hiddenIFrame.addEventListener('load', () => document.body.removeChild(hiddenIFrame));
    hiddenForm.submit();
  });
}