如何使用Notion、SendGrid和Python发送电子邮件(详细教程)

1,046 阅读14分钟

Notion是一个有用的项目管理和笔记的工具。用户可以轻松地创建新的页面和数据库来管理他们的项目。例如,你可以为电子邮件模板创建页面,然后为邮件列表添加一个数据库。为什么你不应该也能用Notion发送电子邮件呢?

在这篇文章中,你将学习如何使用Notion来创建电子邮件模板和邮件列表。使用Python和SendGrid,你将制作一个控制台程序,完成参数解析,这样你就可以有效地发送你的邮件。

前提条件

要继续学习本教程,你将需要:

在你完成先决条件后,你就可以开始学习教程了

设置你的项目环境

在你深入学习代码之前,你需要在你的电脑上设置项目环境。

首先,你需要创建一个存放项目的父目录。在你的电脑上打开终端,导航到一个适合你的项目的目录,输入以下命令,然后点击回车:

mkdir notion-sendgrid-project && cd notion-sendgrid-project

作为Python良好实践的一部分,你还需要创建一个虚拟环境。如果你在 UNIX 或 macOS 上工作,运行下面的命令来创建和激活一个虚拟环境。第一个命令是创建虚拟环境,第二个命令是激活它:

python3 -m venv venv
source venv/bin/activate

然而,如果你在Windows上工作,运行这些命令来代替:

python -m venv venv
venv\bin\activate

在激活你的虚拟环境后,你需要安装以下Python软件包:

要安装这些包,请运行这个命令:

pip install python-dotenv sendgrid requests

作为良好的编程实践的一部分,你会想把敏感信息存储在一个安全的地方。为了做到这一点,你将在一个*.env文件中存储数值作为环境变量。在notion-sendgrid-project目录下,打开一个名为.env*的新文件(注意前面的点),将下面几行粘贴到文件中:

SENDGRID_API_KEY=XXXXXXXXXXXXXXXX
EMAIL_SENDER=XXXXXXXXXXXXXXXX
NOTION_API_TOKEN=XXXXXXXXXXXXXXXX
NOTION_PAGE_ID=XXXXXXXXXXXXXXXX

如果你使用的是GitHub,请确保将*.env*文件纳入你的.gitignore文件。

你的项目环境现在已经设置好了,但你必须先在*.env*文件中配置你的环境变量。"XXXXXXXXXXX "只是其对应变量的占位符值。接下来的章节将介绍如何获得实际值。

获得SendGrid API密钥

在SendGrid仪表板上,点击 "设置">"API密钥"以导航到SendGrid API密钥页面。然后,点击创建API密钥

SendGrid API Keys Page. An arrow is pointed to the Create API Key button

接下来,为API密钥输入名称(我把我的命名为 "Notion")。然后,在API密钥的权限下,选择完全访问。最后,点击 "创建和查看"来创建API密钥:

Create API Key window. The user can set the name and permissions for the API Key

之后,你的API密钥应该在屏幕上弹出。复制该密钥,并在你之前创建的*.env*文件中,用你复制的值替换 "XXXXXXXXXXX "占位符,即SENDGRID_API_KEY

接下来,你需要创建一个经过验证的发件人来发送邮件。在仪表板的左侧,导航到营销>发件人,进入**发件人管理** 页面。点击右上方的创建新发件人按钮,填写必要的字段,然后点击保存。在你之前创建的*.env*文件中,将 "XXXXXXXXXXX "的占位符替换为你为发件人使用的电子邮件地址EMAIL_SENDER 。然后,通过在发件人的邮箱中检查是否有来自SendGrid的邮件来验证发件人。

SendGrid Sender Management page displaying a verified email sender

设置Notion

让我们经历一下如何设置你的Notion以将其与Python集成的步骤。首先,你需要对页面进行格式化。然后,你将创建邮件列表的数据库和一个电子邮件模板。之后,你将创建一个Notion集成,并将你的页面分享给该集成。

格式化Notion的页面

登录Notion后,导航到你想要的工作区。在网页左侧的特色栏目中,点击**+添加一个页面**,向你的工作区添加一个页面。把这个页面命名为 "电子邮件"。

一个名为 "电子邮件"的网页应该被创建。接下来,从电子邮件页面的URL中找到页面ID。这个ID是最后一个破折号后的一个字母数字代码。我的页面的ID在下图中突出显示。

A URL for a Notion page. The page ID portion of the URL is highlighted

你的代码将与上面显示的代码不同。复制页面ID,在你之前创建的*.env*文件中,用你复制的值替换 "XXXXXXXXXXXXX "占位符NOTION_PAGE_ID

现在,将你的光标悬停在左栏的 "电子邮件 "页面上,为邮件列表数据库添加一个页面。应该出现一个加号**+。点击加号"+"来快速添加一个页面。给这个页面命名为 "邮件列表",然后在DATABASE下选择表**作为它的页面类型。

你对 "邮件列表 "的标题的拼写应该完全一样,因为代码是区分大小写的。

接下来,为电子邮件模板添加一个页面。其步骤与之前的步骤类似。将你的光标悬停在左栏的 "电子邮件 "页面上。应该出现一个加号**+。点击加号+**,可以快速在里面添加一个页面。将这个页面命名为 "电子邮件1",然后点击退出窗口。

你的页面组织应该看起来像这样:

Notion page organization for Email page with Mailing List and Email 1 as subpages

你将在下一节中对这些页面进行格式化。

设置邮件列表数据库

点击邮件列表,显示该页面。在页面的右侧,应该有一个菜单。选择+新数据库。页面上会出现一个空表:

An empty table database in Notion titled "Mailing List"

按照以下步骤来格式化该表:

  1. 右键单击列头中的**"名称**",并将其重命名为 "电子邮件"。
  2. 然后,在列头的Tags上点击右键,点击删除属性,删除该列。
  3. 在列头的加号**+**上点击两次,增加两个新列。
  4. 在其中一个新列上点击右键,将其重命名为 "姓名"。
  5. 然后,在第二个新列上点击右键,将其改名为 "姓氏"。

在向你的表格添加一些数值后,你的表格应该是这样的:

Notion table database titled "Mailing List" with fields Email, First Name, and Last Name. There are two entries

确保你的列标题的拼写与上面显示的一致,因为这些特定的标题将在后面的代码中使用。另外,确保你的表格中没有空行!

创建一个电子邮件模板

现在你的数据库已经设置好了,是时候创建一个简单的电子邮件模板了。点击电子邮件1来显示页面。然后,点击该页面,在标题下方点击右键,并输入主题词。我毫无创意地用 "这就是主题词 "作为我的主题词。你还可以通过点击悬停在行的左边出现的小点来改变字体和行的颜色:

Email 1. THIS IS THE SUBJECT LINE

接下来,你需要为电子邮件创建主体文本。按ENTER键,在页面上添加一个新的(Notion的基本组织单位)。然后,键入你的信息。要从列中加入变量,将其定义为{Variable_Name} 。注意,大括号之间没有空格。另外要注意的是,由于按ENTER键会创建一个新的区块,如果要添加一个新的行,你必须按SHIFT + ENTER键。

下面是我的电子邮件模板的一个例子:

An example of an email template in notion.

确保变量名称与上面显示的一致,以继续编写代码。此外,确保模板中只有两个块,因为代码只使用前两个块。

设置Notion集成

接下来,你需要设置一个Notion集成,这样你就可以从Python中访问Notion页面。点击这个链接,进入Notion的 "我的集成 "页面

点击加号**+**瓦片来添加一个集成。为它提供一个名称和一个可选的图像。我把我的命名为 "SendGrid Email",并使用了这张图片。

SendGrid logo

接下来,在 "内容能力"下,取消对 "更新内容"和 "插入内容"的选择。在用户功能下,选择无用户信息。你的权限应该看起来像这样。

Permissions for Notion integration with Read content permission set and No user information for User Capabilities

点击提交,创建整合。之后,内部集成令牌会显示在屏幕上。

Internal Integration Token displayed for newly created Notion integration

复制这个,在你之前创建的*.env*文件中,用你复制的值替换 "XXXXXXXXXXXXX "占位符NOTION_API_TOKEN

Notion的集成不会自动分享到页面。在电子邮件主页面,点击右上角的分享。搜索集成,并点击邀请

Window displaying that the "SendGrid Email" integration was shared with the Email Notion page

现在你已经准备好开始编码应用程序了!

创建应用程序

以下是该应用程序的预期要求:

  • 用户在命令提示符下运行该应用程序。
  • 用户需要指定使用哪个电子邮件模板。
  • 用户可以选择指定使用哪个数据库。
  • 应用程序向预定的收件人发送电子邮件。

notion-sendgrid-project目录下,创建一个名为main.py的新文件。为了遵循预期的要求,复制并粘贴以下代码到main.py中:

import sendgrid
from sendgrid.helpers.mail import Mail
import requests
import os
from dotenv import load_dotenv
import argparse

""" Define variables and initialize argument parser here """

def sendEmail(fromEmail, toEmail, subjectLine, body):
  """ def sendEmail here"""

def main():
  """ get main notion page here """

  """ get email and database IDs here """

  """ get email subject and text here """

  """ access database content here """

  """ send out emails here """

if __name__ == '__main__':
  main()

这段代码可作为应用程序的大纲。必要的模块在顶部被导入,其余的代码将在下面一步步地讲述。然而,如果你想跳到已完成的代码,请转到本节末尾

定义变量并初始化参数解析器

用下面的代码替换main.py中的""" Define variables and initialize argument parser here """

# Define variables and initialize argument parser
load_dotenv()
SENDGRID_API_KEY = os.getenv('SENDGRID_API_KEY')
EMAIL_SENDER = os.getenv('EMAIL_SENDER')
NOTION_API_BASE_URL = 'https://api.notion.com/v1'
NOTION_API_TOKEN = os.getenv('NOTION_API_TOKEN')
NOTION_PAGE_ID = os.getenv('NOTION_PAGE_ID')

parser = argparse.ArgumentParser()
parser.add_argument('--template', type=str, required=True)
parser.add_argument('--mailTo', type=str, default="Mailing List")
args = parser.parse_args()

在上面的代码片断中,环境变量被加载到变量中。此外,这个应用程序使用一个参数分析器来指定使用哪个电子邮件模板和数据库。如果用户有多个模板或数据库可供选择,这将非常有用。在突出显示的几行中,参数解析器被用来添加参数templatemailTotemplate 需要一个参数,而mailTo 的默认值被设置为 "邮件列表"。要看它是如何工作的,请查看题为 "运行应用程序"的部分。

定义sendEmail

main.py中的sendEmail 函数的代码复制并替换为下面的代码:

def sendEmail(fromEmail, toEmail, subjectLine, body):
  message = Mail(
    from_email=fromEmail,
    to_emails=toEmail,
    subject=subjectLine,
    plain_text_content=body)
  try:
    sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
    response = sg.send(message)
    print(response.status_code, response.body, response.headers,sep='\n')
  except Exception as e:
    print(e.message)

这段代码用于创建和发送SendGrid的信息。首先,用参数初始化一个Mail 对象。然后,使用SendGrid辅助库中SendGridAPIClient() 方法将密钥分配给变量sg ,在授权头中使用Bearer token认证将密钥传递给v3 API。然后发送消息,并打印出有关响应的信息。

获取电子邮件模板和数据库的ID

在获得电子邮件模板和数据库的ID之前,你需要首先向主页面Email提出请求。用下面的代码替换main.py中的""" get main notion page here """

  # get main notion page
  url = f"{NOTION_API_BASE_URL}/blocks/{NOTION_PAGE_ID}/children"
  headers = {
    "Accept": "application/json",
    "Notion-Version": "2022-06-28",
    "Authorization": f"Bearer {NOTION_API_TOKEN}"
  }
  response = requests.get(url, headers=headers)
  if response.status_code == 200:
    response_json = response.json()
  else:
    print("Something went wrong when accessing the main notion page")
    return

在上面的代码片段中,应用程序首先通过定义url和header连接到EmailNotion页面。然后,发出一个post请求,从Notion API获得响应,json() 方法被用来格式化和读取响应。

在你收到电子邮件页面的响应后,你可以搜索到电子邮件模板和数据库的ID。用下面的代码替换""" get email and database IDs here """

  # get email and database IDs
  email_id = [x["id"] for x in response_json["results"]\
    if "child_page" in x and x["child_page"]["title"]==args.template]

  if len(email_id) == 0:
    print("Email template not found")
    return
  elif len(email_id) > 1:
    print(f"Warning another email template matches this name: {args.template}")
    input("Press enter to continue...")

  database_id = [x["id"] for x in response_json["results"]\
    if "child_database" in x and x["child_database"]["title"]==args.mailTo]

  if len(email_id) == 0:
    print("Database not found")
    return
  elif len(email_id) > 1:
    print(f"Warning another database matches this name: {args.mailTo}")
    input("Press enter to continue...")

使用列表理解,你能够使用参数解析器的参数来搜索response_json 中的ID。如突出显示的几行所示,在列表理解中设置的条件检查标题是否与相应的参数相匹配。

关于列表理解的更多信息,请参阅Python 数据结构文档中关于列表理解的部分。

获取电子邮件模板的主题和文本

在得到电子邮件模板的页面 ID 之后,你可以使用页面 ID 来访问电子邮件模板的内容。你使用页面ID来格式化API请求的URL。如果请求成功,你可以使用响应来访问邮件模板的内容。用下面的代码替换main.py中的""" get email subject and text here """

  # get email subject and text
  url = f"{NOTION_API_BASE_URL}/blocks/{email_id[0]}/children"
  response = requests.get(url, headers=headers)
  if response.status_code == 200:
    response_json = response.json()
  else:
    print("Something went wrong when accessing the email template")
    return

  subject = response_json["results"][0]\
    ['paragraph']['rich_text'][0]['text']['content']
  emailText = response_json["results"][1]\
    ['paragraph']['rich_text'][0]['text']['content']

访问数据库内容并发送电子邮件

接下来,你将使用数据库ID来访问邮件列表页面的内容并发送邮件。用下面的代码替换main.py中的""" access database content here """

  # access database content
  headers = {
    "Content-Type": "application/json",
    "Notion-Version": "2021-08-16",
    "Authorization": f"Bearer {NOTION_API_TOKEN}"
  }

  url = f"{NOTION_API_BASE_URL}/databases/{database_id[0]}/query"
  response = requests.post(url, headers=headers)
  if response.status_code == 200:
    response_json = response.json()
  else:
    print("Something went wrong when accessing the database")
    return

一个POST请求被用来访问邮件列表数据库的内容。如果你有一双好眼睛,你会注意到headers 中的 "Notion-Version "被重新指定为Notion的一个旧版本。我这样做是因为使用Notion API的 "2021-08-16 "版本更容易访问和格式化数据库内容。

现在你可以访问数据库内容了,你可以使用for-loop函数来发送邮件,sendEmail 。Python的format 方法被用来插值电子邮件模板中定义的变量。要做到这一点,用下面的代码替换main.py中的""" send out emails here """

  # send out emails
  for i in response_json['results']:
    email = i['properties']['Email']['title'][0]['plain_text']
    print(email)
    firstName = i['properties']['First Name']['rich_text'][0]['text']['content']
    lastName = i['properties']['Last Name']['rich_text'][0]['text']['content']
    formattedMessage = emailText.format(First_Name = firstName, Last_Name = lastName)
    sendEmail(EMAIL_SENDER,email,subject,formattedMessage)

完成的代码

这就是了!你完成的代码应该看起来像下面的代码:

import sendgrid
from sendgrid.helpers.mail import Mail
import requests
import os
from dotenv import load_dotenv
import argparse

# Define variables and initialize argument parser
load_dotenv()
SENDGRID_API_KEY = os.getenv('SENDGRID_API_KEY')
EMAIL_SENDER = os.getenv('EMAIL_SENDER')
NOTION_API_BASE_URL = 'https://api.notion.com/v1'
NOTION_API_TOKEN = os.getenv('NOTION_API_TOKEN')
NOTION_PAGE_ID = os.getenv('NOTION_PAGE_ID')

parser = argparse.ArgumentParser()
parser.add_argument('--template', type=str, required=True)
parser.add_argument('--mailTo', type=str, default="Mailing List")
args = parser.parse_args()

def sendEmail(fromEmail, toEmail, subjectLine, body):
  message = Mail(
    from_email=fromEmail,
    to_emails=toEmail,
    subject=subjectLine,
    plain_text_content=body)
  try:
    sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
    response = sg.send(message)
    print(response.status_code, response.body, response.headers,sep='\n')
  except Exception as e:
    print(e.message)

def main():
  # get main notion page
  url = f"{NOTION_API_BASE_URL}/blocks/{NOTION_PAGE_ID}/children"
  headers = {
    "Accept": "application/json",
    "Notion-Version": "2022-06-28",
    "Authorization": f"Bearer {NOTION_API_TOKEN}"
  }
  response = requests.get(url, headers=headers)
  if response.status_code == 200:
    response_json = response.json()
  else:
    print("Something went wrong when accessing the main notion page")
    return

  # get email and database IDs
  email_id = [x["id"] for x in response_json["results"]\
    if "child_page" in x and x["child_page"]["title"]==args.template]

  if len(email_id) == 0:
    print("Email template not found")
    return
  elif len(email_id) > 1:
    print(f"Warning another email template matches this name: {args.template}")
    input("Press enter to continue...")

  database_id = [x["id"] for x in response_json["results"]\
    if "child_database" in x and x["child_database"]["title"]==args.mailTo]

  if len(email_id) == 0:
    print("Database not found")
    return
  elif len(email_id) > 1:
    print(f"Warning another database matches this name: {args.mailTo}")
    input("Press enter to continue...")

  # get email subject and text
  url = f"{NOTION_API_BASE_URL}/blocks/{email_id[0]}/children"
  response = requests.get(url, headers=headers)
  if response.status_code == 200:
    response_json = response.json()
  else:
    print("Something went wrong when accessing the email template")
    return

  subject = response_json["results"][0]\
    ['paragraph']['rich_text'][0]['text']['content']
  emailText = response_json["results"][1]\
    ['paragraph']['rich_text'][0]['text']['content']

  # access database content
  headers = {
    "Content-Type": "application/json",
    "Notion-Version": "2021-08-16",
    "Authorization": f"Bearer {NOTION_API_TOKEN}"
  }

  url = f"{NOTION_API_BASE_URL}/databases/{database_id[0]}/query"
  response = requests.post(url, headers=headers)
  if response.status_code == 200:
    response_json = response.json()
  else:
    print("Something went wrong when accessing the database")
    return

  # send out emails
  for i in response_json['results']:
    email = i['properties']['Email']['title'][0]['plain_text']
    print(email)
    firstName = i['properties']['First Name']['rich_text'][0]['text']['content']
    lastName = i['properties']['Last Name']['rich_text'][0]['text']['content']
    formattedMessage = emailText.format(First_Name = firstName, Last_Name = lastName)
    sendEmail(EMAIL_SENDER,email,subject,formattedMessage)

if __name__ == '__main__':
  main()

继续看下一节,看你如何运行这个应用程序。

运行应用程序

一个参数分析器被用来为应用程序添加更多的功能。你可以通过在--template--mailTo 标志后面添加一个字符串来指定参数的值。要运行代码,在激活了venv虚拟环境的命令提示符下输入以下命令:

python3 main.py --template "Email 1"

你也可以指定一个不同的模板或邮件列表数据库,只要它是在电子邮件页面下。下面是一个例子:

python3 main.py --template "Some Template" --mailTo "Some Database"

下面是我运行应用程序后的命令提示符的截图:

Command prompt showing output from sending emails by running main.py

如果你检查邮件中的收件箱,你应该也已经收到了格式化的邮件:

Email that was received in inbox shows that it was formatted using the template

结论

恭喜你建立了你的应用程序。首先,你创建了一个带有电子邮件模板和邮件列表数据库的Notion页面。然后,使用SendGrid和Python,你从你的命令提示符中向邮件列表发送了邮件。你可以自由地定制你的应用程序。例如,你可以创建/编码更复杂的模板,或者你可以在数据库中添加更多的自定义功能。