惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

Compulsive curiosity, or, how I built an infinite idea machine Gift details on the subscriber portal Portal link in the archive nav The physicists who convinced Fermilab to send Brazil's emails First, add no friction: How micropayments lost and subscriptions won Filter subscribers and automations by source Automations, rebuilt What email will look like in the future Filter subscribers by bounce date and reason Email could have been X.400 times better Three features are moving behind the paywall Firewall changes and improvements Put your name and voice into your company newsletter Simplified email address settings Subscription wall Inboxes were overwhelming before we'd even named them The US government tried really hard to screw up email Public postmortem: database connection exhaustion Ask a nerd: what is the best way to unsubscribe from newsletters? Bookshop.org embeds Email was into agents before they were cool Passwordless login Rename metadata keys in bulk A spring cleaning for our legal docs Ask a nerd: what happens when you click the spam button? Passkey support for two-factor authentication How Buttondown's API versioning works Safer defaults for the email creation API How to send email to space How we enabled Content Security Policy for everyone Recovery codes for two-factor authentication Filter sent emails by engagement rate How we migrated to TypeIDs without breaking clients How we check every link in your email Use newsletter metadata in your emails Should we bring back email exploders? Sort and filter by open and click rates Custom click tracking domains More newsletter settings in the API Revamped replies Custom email templates for everyone Simplified cancellation Ask a Nerd: Does email length affect deliverability? The changelog, reborn Swedish localization Forwarding an email is not always straightforward Public descriptions for tags OpenAPI spec for archives How Rodrigo brings a humanistic view to consumer technology How Brandon Lucas Green shares his music and supports artists Subscribers can come from anywhere. Even another newsletter platform's form. Your newsletter's archives are more valuable than your list Survey responses on the web Better tag self-management Smarter automation filters Granular API keys Ask A Nerd: How does newsletter cadence affect deliverability? New design settings pages Snippets Starred views More ways to customize your archives Inbox filtering Mastodon follower analytics Ask a Nerd: What are good open, click, and response rates for an email newsletter? How we migrated our database to PlanetScale Two new archive themes Ask a Nerd: Does attaching files to your newsletter hurt deliverability? Custom buttons now work in Markdown mode Seline and Tinylytics support Unban subscribers Announcement bars for your archives Public postmortem: archive downtime Bang paths, source routing, and how email trips were planned 2025 disposables.app Russian localization Ask a Nerd: Can you improve email deliverability with a personal domain? More locale options How we interview customers at Buttondown Bluesky analytics Minimum viable complexity Reply to conversations How Jeffery Hicks goes behind-the-scenes in his newsletter Changes to our stack in 2025 2026: Emails Randomize survey answer order TK reminders in the editor What the hell is a UTM? Why we insourced analytics Scroll sync in the editor How Kelly Jensen uses Buttondown to discuss key library issues 2026: Archives How Jamie Thingelstad uses Buttondown to explore tech topics Keeping feature creep at bay Improved filters Content Security Policy in archives Open source Sniperl.ink Auto-activating RSS reader subscriptions What the hell is ActivityPub? Gift subscriptions
Netlify functions + Buttondown
Andrew Stiefel · 2023-03-21 · via

I enjoy the process of designing and building a custom personal website. It’s a great way to experiment with different technologies and have fun learning new tools and concepts along the way.

This time, I was eager to learn how to use serverless functions.

The Challenge

I use a static site generator called Jekyll to build and publish my website. The result is a collection of static HTML, CSS, and JS files that are lightweight, fast, and easy to maintain. It reduces my overhead, both in cost and time because I don’t need to manage a server. But it does have one drawback: I can’t easily handle small background actions, like form submissions.

After all the effort I put into building my personal website, the last thing I wanted to do was slap a pre-designed newsletter subscription form on it. Most email providers offer forms you can quickly embed on your website, but they come with a series of tradeoffs. They are difficult to customize to exactly match your website’s design, and they usually require users to load third-party scripts and styles. Often these third-party resources get blocked by browser privacy settings, and even if they don’t, they can be slow to load.

Eventually, this led me to serverless functions. A serverless function is really just a piece of code that runs like an API endpoint, independently of your website or app. Despite the name, serverless functions do, indeed, run on a server. It’s just a server you don’t have to build or maintain.

The Team: Netlify and Buttondown

I use a service called Netlify to build and deploy my website. It’s key for this project because it publishes the static HTML, CSS, and JavaScript assets from my static site generator to a global CDN and runs the serverless function for my email subscription form.

I’m using Buttondown as my email list provider. Buttondown is a simple, privacy-first newsletter platform. It offers an API with extensive documentation, including an OpenAPI Specification file. Buttondown is free for up to 100 subscribers, after which it costs $9 a month for up to 1,000. The API is available for paying customers starting at the $9/mo plan.

As a side note, Buttondown offers extensive customization options for HTML form embeds, so you can accomplish almost everything in this tutorial with less work. But this is a good starting point to learn how to build serverless functions and as an introduction to creating advanced automation with the Buttondown API.

Here is a brief overview of the steps involved:

  1. Prepare your environment
  2. Create the newsletter subscription form
  3. Write the serverless function
  4. Deploy with Netlify

Ok, let’s get started!

1. Prepare your environment

To begin, you need to configure a few settings within Netlify before you can start building serverless functions.

Configure your Netlify.toml file

First, you need to tell Netlify where it should find and process your serverless functions. If you don’t have one already, create a file in your root directory called netlify.toml. The most basic version should include information about how to build and publish your website. Refer to the Netlify documentation for additional information.

In this example, publish tells Netlify to deploy the HTML and CSS files saved in the public folder for a simple, static website. This example also includes a command Netlify should run (npm install). Netlify will install the single node module dependency you will need later.

[build]
  publish = "public"
  command = "npm install"

Now you can add the path to the functions directory. The default functions directory is YOUR_BASE_DIRECTORY/netlify/functions. You will need to use the node-fetch v3 module later. Due to a change to node-fetch, you will need to enable ESM for the site by adding node_bundler = "esbuild" under the [functions] section of your netlify.toml file:

[functions]
  directory = "netlify/functions/"
  node_bundler = "esbuild"

For this tutorial, your completed netlify.toml file should look like this:

[build]
  publish = "public"
  command = "npm install"

[functions]
  directory = "netlify/functions/"
  node_bundler = "esbuild"

Save your API Key as an environment variable

Next, you must add your Buttondown API Key as an environment variable for your Netlify site. Environment variables are useful to hold information that we don’t want to make public, like API Keys.

Find and create your API Key in Buttondown under Settings > Power Tools > API. Copy this value and then add it as an environment variable using the Netlify Admin interface under Site Settings > Environment Variables.

For this tutorial, we’ll name the key BUTTONDOWN_API_KEY, add the value from Buttondown, and save it.

2) Create the newsletter subscription form

Now that Netlify is set up, I need a way to collect email submissions. The HTML for my email subscription form is very basic to start:

<form name="newsletter" method="POST" action="/success/" data-netlify="true">
  <label for="email">Your Email Address:</label>
  <input type="email" name="email" required />
  <button type="submit">Subscribe</button>
</form>

There are a few important things to note. First, I need to tell Netlify to process the form. All I need to do is add the data-netlify="true" attribute to the form tag.

Next, I must give the form a unique name using the name attribute. In this case, that’s simply name="newsletter". The form’s name attribute determines what shows up in the Netlify interface:

I wanted to redirect users to a confirmation page, so I added action="/success/" to display a page that contains the success message.

The next input in the form is named email. That’s where we collect the email addresses. You could name it anything, but make sure you note what you call it for the next step.

I’ve specified the input type as email and indicated that is required. This means that the browser will do all my validation for me, and won’t let users submit anything besides a valid email address.

Enable bot and spam protection

I’m going to take advantage of the built-in anti-spam and bot protection that comes with Netlify Forms, so I'll tweak the form a bit. I added the netlify-honeypot="bot-field" attribute and created a hidden label and input:

<label class="hidden">Don’t fill this out:</label>
<input class="hidden" name="bot-field" />

This should catch any bots and filter them out before they ever hit my subscriber list. Here is the completed HTML for the newsletter subscription form:

<form
  name="newsletter"
  method="POST"
  action="/success/"
  netlify-honeypot="bot-field"
  data-netlify="true"
>
  <label class="hidden">Don’t fill this out:</label>
  <input class="hidden" name="bot-field" />
  <label for="email">Your Email Address:</label>
  <input type="email" name="email" required />
  <button type="submit">Subscribe</button>
</form>

3) Write the serverless function

Now that I have a working email submission form, it’s time to create the serverless function. To start, create a file in the functions folder called submission-created.js. If you used the default functions directory, you should save your function at YOUR_BASE_DIRECTORY/netlify/functions.

I’m going to use the submission-created event trigger, which will tell Netlify to run my function every time the form is submitted. To accomplish that, I have to make sure the file is named submission-created.js.

Here’s the code for the serverless function:

const { BUTTONDOWN_API_KEY } = process.env;
import fetch from "node-fetch";

exports.handler = async (event, context) => {
  const email = JSON.parse(event.body).payload.email;
  console.log(`Received a submission: ${email}`);

  const response = await fetch("https://api.buttondown.com/v1/subscribers", {
    method: "POST",
    headers: {
      Authorization: `Token ${BUTTONDOWN_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ email }),
  });

  let responseText = await response.text();
  console.log("Response:", responseText);

  return {
    statusCode: 200,
    body: JSON.stringify({}),
  };
};

Let’s look at this line-by-line to understand how it works.

Configure prerequisites

First, I import the Buttondown API key I saved earlier as an environment variable for my function:

const { BUTTONDOWN_API_KEY } = process.env;

On line 2, I import a small library called node-fetch. This allows me to use Javascript’s Fetch API, which is how we’ll format an API POST request to send data to our email service:

import fetch from "node-fetch";

NOTE: When I was writing this post, many of the tutorials available used the require method to import the Fetch API, which resulted in errors when I tried to deploy the function. Make sure you use the method I described above. If you upgrade to node-fetch v3, you'll also need to update either your netlify.toml file or package.json to use ESM.

[functions]
  node_bundler = "esbuild"

You can find more information about how to make a fetch request using node-fetch v3 in an excellent guide by Tatyana Novell on the Netlify blog.

Define the serverless function

Line 4 is where I define the function. The exports.handler value is where Netlify expects to find the function. This is the basic syntax I’ll use to create the serverless function:

exports.handler = async function (event, context) {
  // your server-side functionality
};

Parse the form submission

The first thing I need to do is retrieve the email address from the form submission. I’m going to use JSON.parse. Since I’m using an event to trigger the function, we’ll use event.body to parse the submission and payload.email to retrieve the value of the email input field.

const email = JSON.parse(event.body).payload.email;

Then log the data in the console for debugging:

console.log(`Received a submission: ${email}`);

Create the API request

After retrieving the email address from the event value using JSON.parse, I’m ready to add the email to my list of subscribers using the Buttondown API. I’ll use the node-fetch library I imported earlier to create the POST request.

I need to authorize the API request by passing the BUTTONDOWN_API_KEY environment variable in the headers, and I’ll need to add the email address to the body of the request as a string:

const response = await fetch("https://api.buttondown.com/v1/subscribers", {
  method: "POST",
  headers: {
    Authorization: `Token ${BUTTONDOWN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ email }),
});

Log the response

Then capture and log the response from the email service. We do this to diagnose any issues that happened. Netlify makes it easy to check your function’s logs, so use console.log often!

let responseText = await response.text();
console.log("Response:", responseText);

And finally, we return the response from the function with an empty body:

return {
  statusCode: 200,
  body: JSON.stringify({}),
};

4) Deploy the function

Now that I’ve written my function, configured my netlify.toml file, and added my environment variables, everything is ready to go. Deploying is painless: just set up Netlify’s GitHub integration, push your changes, and your function will be deployed.

Conclusion

It took me less than 50 lines of code to build a custom newsletter subscription form using a serverless function. I wrote it all in HTML, CSS, and JavaScript, and everything is served from my domain. Plus, my website visitors get a nice experience whether they have JavaScript enabled or not, and I’m protected from bot and spam submissions by default.

If you’d like to experiment with creating your own custom newsletter form, you can clone and deploy the GitHub Repository below to Netlify. It comes with a minimal HTML landing page, a simple form, and everything you need to deploy the Netlify Function:

Good luck, and I hope you have fun building a custom newsletter subscription form for your website!

A version of this article originally appeared at andrewstiefel.com on August 28, 2022.