如何用Notion API收集电子邮件注册信息(附代码)

675 阅读10分钟

现在很多人都在建立自己的新闻简报。你已经有了像SubstackMailChimp这样的大公司,像Twitter这样的公司也开始使用Revue,甚至Facebook也开始涉足通讯业务。有些人正试图通过MailPoet自我管理的WordPress解决方案把它带到离家近的地方。

让我们谈谈另一种可能性:用好的老方法收集你自己的电子邮件订户:一个<form> ,提交给一个API,并把它们放入数据存储。

建立通讯的第一个障碍是收集电子邮件的机制。大多数帮助你建立和发送新闻简报的应用程序将提供某种复制和粘贴的注册表例,但这些选项大多是付费的,而我们正在寻找一种免费的、完全由我们托管的东西。

没有什么比建立我们自己的系统更方便的了,因为我们可以控制。在本教程中,我们将在Jamstack架构的基础上建立自己的系统,为我们的新闻简报收集电子邮件。我们将实现一个HTML表单来为我们的新闻简报收集电子邮件,用Netlify函数处理这些电子邮件,并通过Notion API将它们保存到Notion数据库中

首先,Notion

你问什么是Notion?让我们让他们描述一下

Notion是一个单一的空间,你可以在这里思考、写作和计划。捕捉思想,管理项目,甚至管理整个公司--并且完全按照你想要的方式去做。

A screenshot of Notion's homepage. It says Notion is an all-in-one workspace and there is a form to enter an email address and create an account. Black and white illustrations of four people are below the form.

换句话说,Notion有点像一个数据库,但有一个漂亮的视觉界面。数据库中的每一行都有自己的 "页面",在那里写内容与在WordPress的块状编辑器中写博文没什么不同。

这些数据库可以以多种方式可视化,从简单的电子表格到类似Trello的板子,或画廊,或日历,或时间线,或......你懂的。

我们使用Notion是因为我们可以设置表格来存储我们的表单回复,使它基本上成为一个数据存储。Notion最近也恰好发布了一个完整的 API供公众使用。我们可以利用这个API并与之进行交互,使用...

Netlify函数

Netlify Functions是由Netlify的托管平台提供的无服务器API端点。我们可以用JavaScript或Go来编写它们。

A screenshot of the Netlify Functions webpage. It has a large black heading in bold type that reads Build scalable, dynamic applications. There is a teal colored button with rounded corners below to get started with Netlify, then a text link beside it that reads talk to an expert.

我们可以在这里使用Netlify表单来收集表单提交。事实上,Matthew Ström已经分享了这是如何运作的。但是在免费计划中,我们每个月只能收到100个提交的表单。所以,这就是为什么我们要使用Netlify功能--它们每月可以被调用125,000次,这意味着我们每个月可以收集25万封电子邮件,而不用支付一分钱。

让我们来设置Notion数据库

我们需要做的第一件事是在Notion中创建一个数据库。这只需要创建一个新的页面,然后通过输入/table ,插入一个全页面的表块。

Screenshot of a Notion page with the block inserter open and the full page table option selected. The page has a dark background and a sidebar on the left with navigation for the user account.

在Notion中键入一个命令,如/table ,会打开一个上下文菜单,在页面上插入一个块。

让我们给我们的数据库起个名字:Newsletter Emails。

我们想让事情保持简单,所以我们所收集的是当有人提交表格时的电子邮件地址。所以,实际上,我们所需要的只是在表中有一列叫做Email的列。

A screenshot of the Notion database, which is in a spreadsheet or table view. The white heading is titled Newsletter Emails, then there is one column for an email address.

但Notion比这更聪明一些,它包括提供额外信息的高级属性。其中一个是 "创建时间 "属性,正如它的名字一样,它打印了一个新提交的时间的时间戳。

The Notion database showing a second property being added to the database to display the created time of an email entry. A contextual menu is open with a Created time property type selected and an additional contextual menu beside it showing the different types of available properties.

现在,当一个新的 "电子邮件 "条目被添加到表中时,它的添加日期也被显示出来。这对于其他事情来说是很好的,比如计算某人成为订阅者的时间。

我们还需要一个Notion API Token

为了与我们的Notion数据库互动,我们需要创建一个Notion集成并获得一个API令牌。新的整合不是在你的Notion账户中进行的,而是在Notion网站上当你登录到你的账户时进行的。我们将给这个集成一个与Notion表同样有创意的名字。通讯注册。

A plain white webpage for Notion integrations. It has a heading that says Create a new integration with form fields for name, logo, associated workspace, and a submit button.

创建一个新的Notion集成,并将其与Newsletter Emails表所在的工作区关联。

一旦集成被创建,我们就可以获取内部集成令牌(API令牌)。握住它,因为我们稍后会用到它。

The Notion integrations conformation screen displaying the secret internal integration token, which is obscured by the app.

一旦集成被创建,Notion提供了一个秘密的API令牌,用来验证连接。

默认情况下,Notion集成实际上无法访问Notion页面或数据库。我们需要明确地与我们的集成共享特定的页面或数据库,以便集成能够正确地读取和使用表中的信息。点击Notion数据库右上角的共享链接,使用选择器按名称找到集成,然后点击邀请

The Notion database with Share options displayed in a modal with a list of available integrations and a light blue invite button.

接下来是Netlify

我们的数据库已经设置好了,可以开始接收来自Notion的API的表单提交。现在是时候创建一个表单和一个无服务器函数来处理表单提交。

由于我们要在Netlify上建立网站,让我们在本地机器上安装Netlify CLI。打开终端,使用这个命令。

npm install netlify-cli -g

根据你的喜好,我们可以用不同的方式来验证Netlify。你可以按照Netlify的神奇指南作为起点。一旦我们通过了Netlify的认证,我们要做的下一件事就是创建一个Netlify项目。这里有一些命令可以做到这一点。

$ mkdir newsletter-subscription-notion-netlify-function
$ cd newsletter-subscription-notion-netlify-function
$ npm init

我们来写一个Netlify函数

在我们开始写Netlify函数之前,我们需要做两件事。

首先,我们需要安装[@notionhq/client](https://github.com/makenotion/notion-sdk-js) npm包,这是一个官方的Notion JavaScript SDK,用于使用Notion API:

$ npm install @notionhq/client --save

第二,我们需要在项目的根部创建一个.env 文件,并添加以下两个环境变量:

NOTION_API_TOKEN=<your Notion API token>
NOTION_DATABASE_ID=<your Notion database>

数据库的ID可以在其URL中找到。一个典型的Notion页面有一个类似https://www.notion.so/{workspace_name}/{database_id}?v={view_id} 的URL,其中{database_id} 对应于ID。

我们要在functions 目录下编写我们的Netlify函数。所以,我们需要在项目的根部创建一个netlify.toml 文件,告诉Netlify我们的函数需要从这个目录中构建:

[build]
functions = "functions"

现在,让我们在项目的根部创建一个functions 目录。在functions 目录中,我们需要创建一个index.js 文件并添加以下代码。这个函数可以作为一个API端点在/.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 =
    /^(([^&lt;&gt;()[\\]\\\\.,;:\\s@&quot;]+(\\.[^&lt;&gt;()[\\]\\\\.,;:\\s@&quot;]+)*)|(&quot;.+&quot;))@((\\[[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' };
};

让我们逐块地分解一下。

addEmail 函数用于将电子邮件添加到我们的Notion数据库。它需要一个email ,这个参数是一个字符串。

我们创建了一个新的客户端,通过提供我们的NOTION_API_TOKEN 来验证Notion API。我们还设置了日志级别参数,以便在我们开发项目时获得与执行命令有关的信息,一旦我们准备将项目部署到生产中,我们就可以删除这些信息。

在Notion数据库中,每个条目被称为一个页面。因此,我们使用notion.pages.create 方法在我们的Notion数据库中创建一个新条目module.exports.handler 函数被Netlify处理向其发出的请求的函数所使用。

我们首先要检查的是请求方法。如果请求方法不是POST ,我们会发回一个405 - Method not allowed 响应。

然后我们解析前端发送的有效载荷(event.body )对象。如果我们不能解析有效载荷对象,我们会发回一个400 - Bad request 响应。

一旦我们解析了请求的有效载荷,我们就检查是否存在一个电子邮件地址。我们使用validateEmail 函数来检查电子邮件的有效性。如果电子邮件是无效的,我们会发回一个400 - Bad request 响应,并附上一个自定义消息,说明Email is not valid

在我们完成所有的检查后,我们调用addEmail 函数,将电子邮件存储在我们的Notion数据库中,并发回一个200 - Success 响应,表示一切都成功了。

跟随GitHub项目中的代码,提交ed607db

现在是一个基本的HTML表单

我们的无服务器后端已经准备好了,最后一步是创建一个表单,为我们的通讯收集电子邮件提交。

让我们在项目的根目录下创建一个index.html 文件,并在其中添加以下HTML代码。注意,这些标记和类是来自Bootstrap 5:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Notion + Netlify Functions Newsletter</title>
  <link href="<https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css>" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
</head>

<body>
  <div class="container py-5">
    <p>
      Get daily tips related to Jamstack in your inbox.
    </p>

    <div id="form" class="row">
      <div class="col-sm-8">
        <input name="email" type="email" class="form-control" placeholder="Your email" autocomplete="off">
        <div id="feedback-box" class="invalid-feedback">
            Please enter a valid email
        </div>
      </div>
      <div class="col-sm-4">
        <button class="btn btn-primary w-100" type="button" onclick="registerUser()">Submit form</button>
      </div>
    </div>

    <div id="success-box" class="alert alert-success d-none">
      Thanks for subscribing to our newsletter.
    </div>
    <div id="error-box" class="alert alert-danger d-none">
      There was some problem adding your email.
    </div>
  </div>
</body>
<script>
</script>
</html>

我们有一个输入字段,用于收集用户的电子邮件。我们有一个按钮,当点击时,调用registerUser 功能。我们有两个提示,一个是成功提交,一个是不成功提交的提示。

由于我们已经安装了Netlify CLI,我们可以使用netlify dev 命令来在localhost服务器上运行我们的网站。

$ netlify dev

Showing the email form with a heading that says Get daily tips related to Jamstack in your inbox above a form field for an email and a blue button next to it to submit the form. There is a read error message below the email input that reads Please enter a valid email.

一旦服务器启动并运行,我们就可以访问localhost:8888 ,看到我们带有表单的网页。

接下来,我们需要写一些JavaScript,验证电子邮件并向Netlify函数发送一个HTTP请求。让我们在index.html 文件中的<script> 标签中添加以下代码:

function validateEmail(email) {
  const re =
    /^(([^<>()[\\]\\\\.,;:\\s@"]+(\\.[^<>()[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[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());
}

async function registerUser() {
  const form = document.getElementById('form');
  const successBox = document.getElementById('success-box');
  const errorBox = document.getElementById('error-box');
  const feedbackBox = document.getElementById('feedback-box');
  const email = document.getElementsByName('email')[0].value;

  if (!validateEmail(email)) {
    feedbackBox.classList.add('d-block');
    return;
  }

  const headers = new Headers();
headers.append('Content-Type', 'application/json');
  
  const options = {
    method: 'POST',
    headers,
    body: JSON.stringify({email}),
  };

  const response = await fetch('/.netlify/functions/index', options);

  if (response.ok) {
    form.classList.add('d-none');
    successBox.classList.remove('d-none');
  }
  else {
    form.classList.add('d-none');
    errorBox.classList.remove('d-none');
  }
}

registerUser 函数中,我们使用 RegEx 函数检查输入的电子邮件的有效性 (validateEmail)。如果电子邮件无效,我们提示用户重试。一旦我们验证了电子邮件,我们就调用fetch 方法,向我们的Netlify函数发送一个POST 请求,该函数可在/.netlify/functions/index ,其有效载荷对象包含用户的电子邮件。

根据响应,我们向用户显示一个成功提示,否则显示一个错误提示。

获取Mailgun凭证

在我们的数据库中获取邮件是件好事,但还不够,因为我们不知道要如何使用它们。因此,最后一步是在用户注册时向他们发送一封确认邮件。

我们不打算在用户注册后立即发送一个信息。相反,我们将设置一个方法来获取过去30分钟内的电子邮件注册情况,然后发送电子邮件。我认为,在自动生成的电子邮件中添加一个延迟,会使欢迎信息感觉更加个性化。

我们使用Mailgun来发送,但同样,我们也可以使用任何有API的电子邮件服务提供商。让我们访问Mailgun的仪表板,从侧边栏进入设置→API密钥界面。

Showing the Mailgun API security screen. The page has a white background with a dark blue left sidebar that contains navigation. The page displays the private API key, the public validation key, and the HTTP web hook signing key.

我们想要 "私人API密钥"。

让我们复制私人API密钥,并将其添加到.env 文件中,因为。

MAILGUN_API_KEY=<your Mailgun api key>

每封电子邮件都需要来自一个发送地址,所以我们还需要一个东西,那就是发送电子邮件的Mailgun域名。要获得我们的Mailgun域名,我们需要进入发送→域名屏幕。

The Mailgun domains screen showing a table with one row that contains a sandbox URL with a series of statistics.

Mailgun提供了一个用于测试的沙盒域名。

让我们复制我们的Mailgun沙盒域名,并将其添加到.env 文件中,作为。

MAILGUN_DOMAIN=<your Mailgun domain>

发送一封确认邮件

在我们开始编写Netlify发送确认邮件的函数之前,我们需要安装[mailgun-js](https://github.com/mailgun/mailgun-js) npm包,这是一个官方的Mailgun JavaScript SDK,用于使用Mailgun API:

$ npm install mailgun-js --save

我们正在使用/.netlify/functions/index 端点来发送欢迎邮件。因此,在functions 目录中,让我们创建一个新的awelcome.js 文件并添加以下代码:

const { Client, LogLevel } = require('@notionhq/client');
const mailgun = require('mailgun-js');

const {
  NOTION_API_TOKEN,
  NOTION_DATABASE_ID,
  MAILGUN_API_KEY,
  MAILGUN_DOMAIN,
} = process.env;

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

  // Create a datetime that is 30 mins earlier than the current time
  let fetchAfterDate = new Date();
  fetchAfterDate.setMinutes(fetchAfterDate.getMinutes() - 30);

  // Query the database
  // and fetch only entries created in the last 30 mins
  const response = await notion.databases.query({
    database_id: NOTION_DATABASE_ID,
    filter: {
      or: [
        {
          property: 'Added On',
          date: {
            after: fetchAfterDate,
          },
        },
      ],
    },
  });

  const emails = response.results.map((entry) => entry.properties.Email.title[0].plain_text);

  return emails;
}

async function sendWelcomeEmail(to) {
  const mg = mailgun({ apiKey: MAILGUN_API_KEY, domain: MAILGUN_DOMAIN });

  const data = {
    from: `Ravgeet Dhillon <postmaster@${MAILGUN_DOMAIN}>`,
    to: to,
    subject: 'Thank you for subscribing',
    text: "Thank you for subscribing to my newsletter. I'll be sending daily tips related to JavScript.",
  };

  await mg.messages().send(data);
}

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

  const emails = await fetchNewSignups();

  emails.forEach(async (email) => {
    await sendWelcomeEmail(email);
  });

  return { statusCode: 200, body: 'ok' };
};

上面的代码有两个主要的功能,我们需要理解。首先,我们有一个fetchNewSignups 函数。我们使用Notion SDK客户端来获取我们Notion数据库中的条目。我们使用了一个过滤器,只获取那些在过去30分钟内添加的邮件。一旦我们从Notion API得到一个响应,我们就会循环这个响应并将电子邮件映射到一个emails 数组中。

其次,我们有一个sendWelcomeEmail 函数。这个函数用于通过Mailgun API向新的订阅者发送一封电子邮件。

为了将所有的事情整合在一起,我们在module.exports.handler 函数中循环查看新的电子邮件,并向每个新的电子邮件发送一封欢迎邮件。

这个函数可在localhost:8888/.netlify/functions/welcome

请跟随GitHub项目中的代码,提交b802f93

让我们来测试一下!

现在我们的项目已经准备好了,我们需要做的第一件事就是测试这个表单,看看电子邮件是否存储在我们的Notion数据库中。让我们去localhost:8888 ,输入一个有效的电子邮件地址。

很完美!我们得到了成功的消息。现在让我们到Notion数据库去看看这个电子邮件地址是否被捕获。

太棒了,它就在那里!我们还可以看到,Notion已经自动填充了Added On字段,这正是我们想要的。

接下来,让我们从Postman发送一个POST 请求到localhost:8888/.netlify/functions/welcome ,并检查我们的收件箱中的订阅确认信息。

Woah!我们已经收到了订阅通讯的欢迎邮件。

在你的收件箱中没有看到该邮件?请确保检查你的垃圾邮件文件夹。

下一步是什么?

从这里开始,下一步将是设置一个脚本,每30分钟运行一次并调用欢迎终端。到目前为止,Netlify并没有提供任何运行CRON作业的方法。所以,我们可以设置GitHub Actions来处理它。

另外,我们可以使用安全机制来保护欢迎端点,比如一个简单的密码或JWT令牌。这将防止互联网上的任何人调用welcome 端点,向我们的订阅者发送大量的垃圾邮件。

所以,我们有了它!我们成功地实现了Jamstack的方式来收集通讯的电子邮件。做这样的事情最大的好处是,我们可以一下子学到很多不同的技术。我们把收集电子邮件地址的服务和API连在一起,验证提交的信息,发布到数据库,甚至发送电子邮件。我们可以用相对较少的东西做这么多事情,这非常酷。

我希望你喜欢阅读并从这篇文章中学到一些东西,就像我喜欢为你写这篇文章一样。请在评论中告诉我你是如何使用这种方法来获得自己的利益的。编码愉快!