Collecting Email Signups With the Notion API

A lot of people these days are setting up their own newsletters. You’ve got the current big names like Substack and MailChimp, companies like Twitter are getting into it with Revue, and even Facebook is getting into the newsletter business. Some folks are trying to bring it closer to home with a self-managed WordPress solution via MailPoet.
Let’s talk about another possibility: collecting your own email subscribers the good ol’ fashioned way: a

that submits to an API and puts them into data storage.

The first hurdle in setting up a newsletter is a mechanism to collect emails. Most apps that help you with building and sending newsletters will offer some kind of copy-and-paste signup form example, but most of these options are paid and we are looking for something that is free and completely hosted by us.
Nothing is handier than setting up our own system which we can control. In this tutorial, we will set up our own system to collect emails for our newsletters based on the Jamstack architecture. We will implement an HTML form to collect emails for our newsletter, process those emails with Netlify Functions, and save them to a Notion database with the Notion API.
But before we begin…
We need the following things:
All of these services are free (with limits). And as far as Mailgun goes, we could really use any email service provider that offers an API.
Get the entire code used in this tutorial over at GitHub.
First off, Notion
What is Notion, you ask? Let’s let them describe it:
Notion is a single space where you can think, write, and plan. Capture thoughts, manage projects, or even run an entire company — and do it exactly the way you want.
In other words, Notion is sort of like a database but with a nice visual interface. And each row in the database gets its own “page” where writing content is not unlike writing a blog post in the WordPress block editor.
And those databases can be visualized in a number of ways, from a simple spreadsheet to a Trello-like board, or a gallery, or a calendar, or a timeline, or a… you get the picture.
We are using Notion because we can set up tables that store our form responses, making it basically a data store. Notion also just so happened to recently release a full API for public use. We can take advantage of that API and interact with it using…
Netlify Functions
Netlify Functions are serverless API endpoints provided by, of course, Netlify’s hosting platform. We can write them in JavaScript or Go.
We could use Netlify forms here to collect form submissions. In fact, Matthew Ström already shared how that works. But on a free plan, we can only receive 100 submissions per month. So, that’s why we’re using Netlify Functions — they can be invoked 125,000 times a month, meaning we can collect a quarter-million emails every month without paying a single penny.
Let’s set up the Notion database
The first thing we need to do is create a database in Notion. All that takes is creating a new page, then inserting a full-page table block by typing /table.
Typing a command in Notion, like /table, opens up a contextual menu to insert a block on a page.Let’s give our database a name: Newsletter Emails.
We want to keep things pretty simple, so all we’re collecting is an email address when someone submits the form. So, really, all we need is a column in the table called Email.
But Notion is a little smarter than that and includes advanced properties that provide additional information. One of those is a “Created time” property which, true to its name, prints a timestamp for when a new submission is created.
Now, when a new “Email” entry is added to the table, the date it was added is displayed as well. That can be nice for other things, like calculating how long someone has been a subscriber.We also need a Notion API Token
In order to interact with our Notion database, we need to create a Notion integration and get an API token. New integrations are made, not in your notion account, but on the Notion site when you’re logged into your account. We’ll give this integration a name that’s equally creative as the Notion table: Newsletter Signups.
Create a new Notion integration and associate it with the workspace where the Newsletter Emails table lives.Once the integration has been created, we can grab the Internal Integration Token (API Token). Hold onto it because we’ll use it in just a bit.
Once the integration has been created, Notion provides a secret API token that’s used to authenticate connections.By default, Notion Integrations are actually unable to access Notion pages or databases. We need to explicitly share the specific page or database with our integration in order for the integration to properly read and use information in the table. Click on the Share link at the top-right corner of the Notion database, use the selector to find the integration by its name, then click Invite.
Next is Netlify
Our database is all set to start receiving form submissions from Notion’s API. Now is the time to create a form and a serverless function for handling the form submissions.
Since we are setting up our website on Netlify, let’s install the Netlify CLI on our local machine. Open up Terminal and use this command:
npm install netlify-cli -g
Based on your taste, we can authenticate to Netlify in different ways. You can follow Netlify’s amazing guide as a starting point. Once we have authenticated with Netlify, the next thing we have to do is to create a Netlify project. Here are the commands to do that:
$ mkdir newsletter-subscription-notion-netlify-function
$ cd newsletter-subscription-notion-netlify-function
$ npm init
Follow along with this commit in the GitHub project.
Let’s write a Netlify Function
Before we get into writing our Netlify function, we need to do two things.
First, we need to install the [@notionhq/client](https://github.com/makenotion/notion-sdk-js) npm package, which is an official Notion JavaScript SDK for using the Notion API.
$ npm install @notionhq/client –save
Second, we need to create a .env file at the root of the project and add the following two environment variables:
NOTION_API_TOKEN=
NOTION_DATABASE_ID=
The database ID can be found in its URL. A typical Notion page has a URL like https://www.notion.so/{workspace_name}/{database_id}?v={view_id} where {database_id} corresponds to the ID.
We are writing our Netlify functions in the functions directory. So, we need to create a netlify.toml file at the root of the project to tell Netlify that our functions need to be built from this directory.
[build]
functions = “functions”
Now, let’s create a functions directory at the root of the project. In the functions directory, we need to create an index.js file and add the following code. This function is available as an API endpoint at /.netlify/functions/index.
const { Client, LogLevel } = require(‘@notionhq/client’);

const { NOTION_API_TOKEN, NOTION_DATABASE_ID } = process.env;

async function addEmail(email) {
// Initialize Notion client
const notion = new Client({
auth: NOTION_API_TOKEN,
logLevel: LogLevel.DEBUG,
});

await notion.pages.create({
parent: {
database_id: NOTION_DATABASE_ID,
},
properties: {
Email: {
title: [
{
text: {
content: email,
},
},
],
},
},
});
}

function validateEmail(email) {
const re =
/^(([^<>()[\]\\.,;:\[email protected]"]+(\.[^<>()[\]\\.,;:\[email protected]"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}

module.exports.handler = async function (event, context) {
// Check the request method
if (event.httpMethod != ‘POST’) {
return { statusCode: 405, body: ‘Method not allowed’ };
}

// Get the body
try {
var body = JSON.parse(event.body);
} catch (err) {
return {
statusCode: 400,
body: err.toString(),
};
return;
}

// Validate the email
const { email } = body;
if (!validateEmail(email)) {
return { statusCode: 400, body: ‘Email is not valid’ };
}

// Store email in Notion
await addEmail(email);
return { statusCode: 200, body: ‘ok’ };
};

Let’s break this down, block by block.
The addEmail function is used to add the email to our Notion database. It takes an email parameter which is a string.
We create a new client for authenticating with the Notion API by providing our NOTION_API_TOKEN. We have also set the log level parameter to get information related to the execution of commands while we are developing our project, which we can remove once we are ready to deploy the project to production.
In a Notion database, each entry is known as a page. So, we use the notion.pages.create method to create a new entry in our Notion database. The module.exports.handler function is used by Netlify functions that handle the requests made to it.
The first thing we check is the request method. If the request method is something other than POST, we send back a 405 – Method not allowed response.
Then we parse the payload (event.body) object sent by the front end. If we are unable to parse the payload object, we send back a 400 – Bad request response.
Once we have parsed the request payload, we check for the presence of an email address. We use the validateEmail function to check the validity of the email. If the email is invalid, we send back a 400 – Bad request response with a custom message saying the Email is not valid.
After we have completed all the checks, we call the addEmail function to store the email in our Notion database and send back a 200 – Success response that everything went successfully.
Follow along with the code in the GitHub project with commit ed607db.
Now for a basic HTML form
Our serverless back-end is ready and the last step is to create a form to collect email submissions for our newsletter.
Let’s create an index.html file at the root of the project and add the following HTML code to it. Note that the markup and classes are pulled from Bootstrap 5.






Notion + Netlify Functions Newsletter

Get daily tips related to Jamstack in your inbox.

Please enter a valid email

Thanks for subscribing to our newsletter.
There was some problem adding your email.




We have an input field for collecting user emails. We have a button that, when clicked, calls the registerUser function. We have two alerts that are shown, one for a successful submission and one for an unsuccessful submission.
Since we have installed Netlify CLI, we can use the netlify dev command to run our website on the localhost server:
$ netlify dev
Once the server is up and running, we can visit localhost:8888 and see our webpage with the form.Next, we need to write some JavaScript that validates the email and sends an HTTP request to the Netlify function. Let’s add the following code in the