The Simplest Way to Collect Emails for a Waitlist Using Github and a CSV Fileimage
All Stories

The Simplest Way to Collect Emails for a Waitlist Using Github and a CSV File

Launching a landing page is one of the best things we can do for a new idea. Writing the content forces us to identify, evaluate, and distill the core concepts down to a few headlines and images. This process helps us understand our idea before we start bringing that idea to life.

Obviously though, a well-crafted landing page's main purpose is to communicate that idea with a community and call them to action. And before launching, most of the time that call to action is simply, "Give us your email and we'll notify you when we launch".

But in my experience, creating a system for actually storing those emails can be a time consuming process. It requires choosing an external email service like MailChimp, ConvertKit, or SendGrid. It requires making a new email so we can create a new account and email list. And if we're collaborating with a team, it might also require paying for a product tier that allows multiple users on one account.

None of these steps are complicated, but these compounding decisions have a way of slowing us down. And in the early stages of a project, the most important thing to keep is momentum.

So, in this article I'm going to share my new laughably simple workflow for capturing emails from a landing page. It doesn't require any external mail service, and by copying the code snippets down below, it can be up and running in any Next.js, Astro, or Remix project in less than 10 minutes.

Here's the plan...

🤭 Store the emails in a CSV on GitHub.

Seriously. That's the whole plan. When someone submits their email on our landing page, we'll use GitHub's API to append the email to a CSV file that lives in a private repository. Note that it's very important to make this repository private. If we don't, we'll be leaking our community members' emails onto the open web.

To connect to that private repository, we'll need to create a Personal Access Token on GitHub and add it as an environment variable in our project. Then, we can instantiate Octokit (GitHub's JS API wrapper library) with that token to use it in requests to the API:

import { Octokit } from "octokit";
import { env } from "./env";

const octokit = new Octokit({
  auth: env.GITHUB_TOKEN,

After that, the actual code to commit the email to the file is a little convoluted, but not complicated.

// Function that updates a remote CSV file
// stored in a Github repository
export async function updateCSV(email: string) {
  // Get the file in the repo so we can get the
  // last commit SHA
  const getFile = await octokit.request(
    "GET /repos/{owner}/{repo}/contents/{path}",
      owner: env.GITHUB_REPO_OWNER,
      repo: env.GITHUB_REPO_NAME,
      path: 'emails.csv',
      headers: {
        "X-GitHub-Api-Version": "2022-11-28",
  const fileRef = as GitRef;
  const fileSHA = fileRef.sha;
  // Get the current contents of the file use
  // `atob` because the contents is Base64 encoded
  const fileContent = atob(fileRef?.content || "");

  // Make the request to add a new commit
  await octokit.request("PUT /repos/{owner}/{repo}/contents/{path}", {
    owner: env.GITHUB_REPO_OWNER,
    repo: env.GITHUB_REPO_NAME,
    path: 'emails.csv',
    message: `${email} added`,
    // Base64 encode the new file contents
    content: btoa(fileContent + `\n${email},`),
    // Pass in the previous commit SHA
    sha: fileSHA,
    headers: {
      "X-GitHub-Api-Version": "2022-11-28",

And that's all the code we need to take an email and append it to a CSV on GitHub. Now, we can use this function in a form action response handler inside Next, Astro, or Remix like so:

import type { APIRoute } from "astro";
import { updateCSV } from "../updateCSV";

export const POST: APIRoute = async ({ request }) => {
  const data = await request.formData();
  const email = data.get("email");
  // Validate the data - you'll probably want to do more than this
  if (!email) {
    return new Response(
        message: "Whoops! Please provide a valid email.",
      { status: 400 },

 👉 updateCSV(email.toString()); 👈
  return new Response(
        "🎉 Welcome to the waitlist! Please check your email for more information about getting started with Repbot.",
    { status: 200 },

And just like that, we can start collecting emails for our latest side project! I've been really enjoying using this system for the past few weeks and there are a few key benefits and gotchas that I want to highlight.

💡 New project? New repo.

When I previously wanted to capture a new list of emails for a project, I had to either spoof my email or create an entirely new one to use services like MailChimp, ConvertKit, or SendGrid.

But now, when I want to start a new list, all I have to do is create a new private repository and I'm all set. No need for an external email provider until I actually want to send emails to those that sign up. This simplified process saves me a ton of time and focuses my attention on the real task at hand: launching the landing page.

🤝 Sharing is free.

Usually before full launch, we'll want to reach out to a few people on our waitlist to get early feedback on our projects. And if we're working with a team, this is a great thing for the less technical people to do while the others keep building.

For me of the past, that meant having shared credentials to an email provider so that those everyone can access the list of emails. That's a big security vulnerability. However, now that the emails are just in a CSV on GitHub, giving access to the list is as easy as adding people to the repository. Secure, scaleable, and simple.

‼️ Damn duplicates.

The one major shortcoming I've found with this system is handling duplicates. Because our logic simply appends to the list without checking if that email is already in the list, we can end up with rows of the same email if people resubmit or spam the form.

However, this is more of an annoyance than a real problem - since we aren't actually going to be sending email from this list. Instead, when we bring this CSV into a program like Sheets, Excel, or Numbers, we can clean the duplicates before doing a mail merge. And if we're just going to import these emails into a mail sender like SendGrid of ConvertKit in the future, then they'll handle getting rid of duplicates for us.

💋 Go forth and K.I.S.S.

I hope you found this article interesting and consider using this system for capturing emails in your next side project. I promise it'll help you maintain momentum on the thing that actually matters: co-creating with your community.

If you do end up trying this out, let me know what you think of it by shooting me an email or sending me a DM on LinkedIn. And if you want to be notified the next time I publish an article, sign up for my newsletter using the form down below.

Until next time.

Drew Lyton
Drew Lyton
Monday, October 2, 2023

I'm a software engineer, ex-founder and writer who cares deeply about creating a better relationship between technology and society. I'm currently writing a book about early stage entrepreneurship.


If this article has been valuable and you'd like to be notified the next time I post, subscribe to my newsletter!

Each week, I share my latest post along with three other works from people smarter than me on whatever topic is top of mind. Of course, you can unsubscribe at anytime.