Build your own SvelteKit Boilerplate: Using Mailchimp with Endpoints

If you build it, they will come. That’s a myth that many developers believe.

A product so good that it sells itself sounds good. But it’s also a convenient way to hide from having to do something scary.

Talk to people. Make sales.

We can go halfway there. We will need to get out and sell the product, but not today!

Going halfway there in this case just means making a little form. So let’s get started.

I did another stream 😀

Stream no longer available

Again, you can get the code at csellis/svelte-backpack

If you have any questions, you can reach out to me at @elliscs or Chris Ellis.

Integrating Mailchimp using SvelteKit Endpoints

There’s a big dilemma in SvelteKit land at the moment. You see, it’s built on Vite. Both are really amazing to work with. They are pushing the web forward.

However, it can get pretty damn tricky working with SDKs.

There are a ton of SDKs out there that are built with CommonJS and Vite doesn’t currently work well with them.

If you want to go down a rabbit hole, check out https://github.com/vitejs/vite/issues/2579

The net result is that we have to use plain old REST using the Fetch API.

What we’ll do

  1. Add an input field to get user emails & hit an endpoint with Fetch
  2. Construct our endpoint
  3. Create an email service that we use for Mailchimp

Adding an Input

// src/routes/index.svelte I should probably break this out, right?

<span>&lt;<span>script</span>&gt;</span><span>
    <span>import</span> Icon, { Code, Briefcase } <span>from</span> <span>'svelte-hero-icons'</span>;

    <span>let</span> email = <span>''</span>;

    <span>async</span> <span><span>function</span> <span>handleSubmit</span>(<span></span>) </span>{
        <span>let</span> body = {
            email
        };
        <span>let</span> result = <span>await</span> fetch(<span>'/api/registerEmail.json'</span>, {
            <span>method</span>: <span>'post'</span>,
            <span>body</span>: <span>JSON</span>.stringify(body)
        });
        <span>const</span> registerEmailResponse = <span>await</span> result.json();
        <span>if</span> (registerEmailResponse.status === <span>200</span>) {
            email = <span>''</span>;
        }
    }
</span><span>&lt;/<span>script</span>&gt;</span>

We set our email and then we handleSubmit sending email to our yet to be written route, api/registerEmail.json.

&lt;form <span>on</span>:submit|preventDefault={handleSubmit}&gt;
    &lt;<span>input</span> <span>type</span>="text" bind:<span>value</span>={email} /&gt;
        &lt;button
            <span>type</span>="submit"
            <span>class</span>="btn bg-secondary-600 hover:bg-secondary-700 text-white text-2xl font-normal px-6 py-4"&gt;
               <span>Start</span> Building
        &lt;/button&gt;
&lt;/form&gt;

We added a form and change the Start Building button from a link to a submit button.

Creating our Endpoint

<span>// src/routes/api/registerEmail.json.js</span>
<span>import</span> mailchimp <span>from</span> <span>'$lib/services/mailchimp'</span>;

<span>export</span> <span>async</span> <span><span>function</span> <span>post</span>(<span>{ body }</span>) </span>{
    <span>try</span> {
        <span>let</span> data = <span>await</span> <span>JSON</span>.parse(body);
        <span>let</span> result = <span>await</span> mailchimp.registerEmail(data.email);
        <span>return</span> {
            <span>status</span>: result.status,
            <span>body</span>: result
        };
    } <span>catch</span> (error) {
        <span>console</span>.error(error);
    }
}

That file name looks trippy, amirite?

They explain it more completely here, but basically, we’re creating our own HTTP endpoints right in the directories we plan to use them.

We parse the data we send to the route then forward that to our Mailchimp service that we are about to create.

Why not just handle it right here?

Great question, unknown developer!

There’s a strong likelihood I will want to extend the Mailchimp integration and it’s a nice developer experience having it all available within my own Mailchimp service. This also puts access to my Mailchimp API key another step back from my front end.

In the stream, I asked myself a few times whether I thought the keys would be exposed even here.

To quote a developer from the Svelte Discord channel:

If it is imported to component within route, yes. It will be exposed. If You import it to endpoints or hooks than it will not be exposed

Creating our Mailchimp Service

<span>import</span> dotenv <span>from</span> <span>'dotenv'</span>;
<span>import</span> base64 <span>from</span> <span>'base-64'</span>;
dotenv.config();
<span>let</span> MAILCHIMP_API_KEY = process.env[<span>'MAILCHIMP_API_KEY'</span>];

<span>async</span> <span><span>function</span> <span>registerEmail</span>(<span>email</span>) </span>{
    <span>try</span> {
        <span>// substitute your Mailchimp settings here</span>
        <span>let</span> dc = <span>''</span>;
        <span>let</span> list_id = <span>''</span>;
        <span>let</span> url = <span>`https://<span>${dc}</span>.api.mailchimp.com/3.0/lists/<span>${list_id}</span>/members`</span>;
        <span>let</span> password = MAILCHIMP_API_KEY;

        <span>let</span> data = {
            <span>email_address</span>: email,
            <span>status</span>: <span>'unsubscribed'</span>
        };

        <span>let</span> headers = <span>new</span> Headers();
        headers.append(<span>'Authorization'</span>, <span>'Basic '</span> + base64.encode(<span>'anystring:'</span> + password));

        <span>const</span> response = <span>await</span> fetch(url, {
            <span>method</span>: <span>'POST'</span>,
            <span>headers</span>: headers,
            <span>body</span>: <span>JSON</span>.stringify(data)
        });
        <span>const</span> mailchimpResponse = <span>await</span> response.json();
        <span>if</span> (mailchimpResponse) {
            <span>return</span> mailchimpResponse;
        }
    } <span>catch</span> (error) {
        <span>console</span>.error(error);
    }
}

<span>const</span> mailchimp = {
    registerEmail
};

<span>export</span> <span>default</span> mailchimp;

We first need to set and pull in environment variables. I’ll leave that up to you to create a .env file with MAILCHIMP_API_KEY as a key.

I can’t really show me accessing my key either. I’ve been burned before streaming a key online. Live and learn.

Then we assemble the code. The URL code that is. With Headers.

We need to encode our password (API key) with Base64. We also have to convert our data to the style object Mailchimp’s API creates. Lastly, we curry the response back down to the browser.

We’ve scaffolded out a way for our boilerplate/starter to collect email addresses. This functionality is vitally important for many websites.

We also have a great model for moving forward. We have an example API call we can use and also an example of using services within a SvelteKit app.

We need to handle the response back down to the browser. There’s a huge problem though.

Right now our API is completely open which means a robot could hit it 1k times a second and blow out our free budget of functions and probably lock our Mailchimp. In the next one, I want to explore making this much more difficult.

I would expect your needs might differ from mine. Please let me know what you’d like to see next in this boilerplate at @elliscs or Chris Ellis.

The repo is available at github.com/csellis/svelte-backpack. Feel free to fork, comment, star, etc. Heads up, this is not its final form.

Also, send me a DM if anything was confusing or needs to be reworked.