Wed Jan 11 2023

Using Supabase to store a subscriber list

Why were there no waiters at the developer conference?

Because it was a serverless function.

Ok, now that I’ve got that out of my system I’d like to tell you all about how I used Supabase to manage a subscriber list for my blog.

The first question you might be wondering is why you’d got through the bother of setting up a database for storing subscribers when there are so many third-party providers that do exactly this ( Mailchimp and Buttondown are 2 popular options). It’s a question that I asked myself too!

There are a few reasons why I decided to store my subscribers independently in my own database:

  1. I’m indecisive and didn’t know exactly how I was going to use the data. Self hosting the data is a lot more flexible than using a third-party provider;
  2. I want to spend as little money as possible! Supabase is very generous on their free tier so it suits me nicely.
  3. This is a personal blog so part of the fun is learning new things and building things myself!

Ok, great, but why Supabase?

Let’s be real - I’m not a backend dev. My knowledge of databases is minimal and I’m totally fine with that. I think that services like Firebase, Supabase, and Mongo Atlas are made for people like me. Yes, they all provide slightly different flavours of database: relational, non-relational, JSON. But really that doesn’t matter so much to me. What matters to me with these services is that they provide a simple database structure, an easy to use API, and minimal setup. All three of the solutions I mentioned fit this brief, however this time I went with Supabase because it’s the new kid on the block and I wanted to try it out (so far it’s been great!).

The Code

There’s nothing too complex going on here, just a simple HTML form element with a few inputs.

<!--HTML -->
<form id="subscribe-form" name="subscribe">
		<label for="name-field"> Name: </label>
			placeholder="Colonel Mustard"
	<fieldset class="visually-hidden" aria-hidden="true">
		<label for="last-name-subscribe-field">Last Name:</label>
		<label for="email-field">Email:</label>
	<input type="submit" value="Subscribe" />

🤔 But hold up, what’s with the visually-hidden class on the middle fieldset? That’s a honeypot ! It’s a hidden field that’s designed to trick bots into filling it out. If the field is filled out then we know we’ve caught a bot and we can ignore the submission.

I’ve added the honeypot to a believable field with the name attribute of last-name (I’m not collecting a last name, but bots don’t know this!). Bots are getting more and more clever so don’t go naming the field “honeypot” or there’s a chance that a smart bot won’t fall for your trap!

The visually-hidden class applies the following CSS, which just yeets the fieldset off the left-hand side of the page - out of view for any human. It’s also got a a tabindex of -1 so that it’s not focusable by keyboard users, and aria-hidden="true" so that it isn’t announced by screen readers.

/* CSS */
.visually-hidden {
	position: absolute;
	left: -9999px;

Next up we’ve got some JS to handle the form submission and shoot the data off to our serverless function via Netlify . I’ve heard great things about Vercel too, so you could try Vercel, but my experience with Netlify has been excellent so I’ll be sticking with them.

// JS

const subscribeForm = document.getElementById("subscribe-form");

subscribeForm.addEventListener("submit", async event => {
	// We are using JS so don't refresh the page.

	// Format the form data into an object
	const formData = Object.fromEntries(new FormData(subscribeForm));

	// Send the data to our netlify serverless function
	await fetch("/.netlify/functions/add-subscriber", {
		method: "POST",
		body: JSON.stringify({
			last_name: formData.last_name
	}).then(response => response.json());

	// Reset the form

The Object.fromEntries(new FormData(subscribeForm)); is a neat trick that takes the form data and converts it into an object like this:

// JS
	name: "Colonel Mustard",
	email: "",

From here the netlify serverless function can receive our fetch request and respond accordingly.

import * as dotenv from "dotenv";
import { createClient } from "@supabase/supabase-js";


// Create a single supabase client for interacting with your database
const supabase = createClient(process.env.DATABASE, process.env.DATABASE_KEY);

export const handler = async (event, context) => {
	// De-structure the data from the request
	const { email, name, last_name } = JSON.parse(event.body);

	// We are expecting last_name to be empty
	// If not, we've caught a bot!
	if (last_name) {
		return {
			statusCode: 500,
			body: JSON.stringify({
				status: 500,
				message: "Honeypot triggered"

	// Send the data to supabase
	const { data, error } = await supabase
		.insert({ email, name });

	return {
		statusCode: 200,
		body: JSON.stringify({
			status: 200,
			message: data,
			error: error

If you want a full explanation of how the above code works you can check out the docs .

Notice that if there is anything in the last_name field I immediately return a 500 status and don’t hit the supabase database. If the data looks good we send it to the Supabase database and return a 200 status code.


So, what did we achieve here? We’ve built a simple form that collects names and email adresses and stores them in a database. We’ve also added a honeypot to catch bots and prevent our database filling up with junk data. So what?

Well, effectively we’ve built half of a newsletter service. The fun part comes next where we’ll hook up a service for sending emails to subscribers using the email addresses we’ve been collecting using the subscribe form! Keep an eye out for the next post. Or better yet…

Subscribe using the form below!

Add a comment   ✍️