Notionのデータベースにレコードが追加されたらメールを送信する
Notionは最近注目度の高い情報共有サービスです。Wikiのように自由テキストを保存したり、データベースのように構造化されたデータを保存もできます。
今回はそんなNotion内にあるメールアドレスを使って、blastengineと組み合わせてみました。データベースのデータ追加をモニタリングし、そのデータを使ってメール送信します。
機能について
- データベースにあるデータを使ってメール送信・ステータスの更新
- バウンス情報をNotionのデータに反映
前半となるこの記事ではデータベースにあるデータを使ったメール送信・ステータスの更新について実装します。
ユーザ登録する
blastengineにユーザ登録します。管理画面に入るためのユーザID、パスワードが手に入るので、ログインします(ユーザIDは後で使います)。
送信元ドメインのSPFを設定する
送信元として利用するドメイン(自分で持っているもの)の設定をします。これは任意のドメイン管理サービスで設定できますが、TXTレコードに以下のSPFを追加します。
txt @ v=spf1 include:spf.besender.jp ~all
APIキーを取得する
ログイン後、管理画面の右上にある設定メニューに移動します。
そして設定の中で、APIキーを取得します。
データベースを作成する
作成するデータベースでは、お問い合わせフォームに必要な項目を登録しておく必要があります。今回は以下の項目を作成しました。
項目名 | 型 |
---|---|
会社名 | テキスト |
名前 | テキスト(タイトル) |
メールアドレス | メールアドレス |
件名 | テキスト |
送信状態 | ステータス |
バウンス | テキスト |
配信ID | 数字 |
エラーメッセージ | テキスト |
送信状態は「作成中」「送信待ち」「送信完了」の3つのステータスがあります。
Notionインテグレーションを作る
Notionのインテグレーションページにて、新しいインテグレーションを作成します。今回は blastengine
としています。名前やワークスペースを選択すればできあがりです。
内部インテグレーショントークン
というのが生成されるので、メモしておきます。
インテグレーションを追加する
先ほど追加したデータベースに対してNotionインテグレーションを追加します。
右上にある三点リーダーよりコネクトの追加を選択し、作成したインテグレーション(今回は blastengine
を追加します)。
アプリを作る
今回はNotionのJavaScript SDKを使います。これはNode.js向けに提供されています。まず適当なフォルダを作成します。
mkdir notion-app
cd notion-app
Node.jsプロジェクトとして初期化します。Node.jsはインストール済みであることとします。
npm init -y
ライブラリをインストール
必要なライブラリをインストールします。
- @notionhq/client
Notion SDK - blastengine
blastengine SDK - dotenv
環境変数の追加
開発用ライブラリは以下の通りです。
- TypeScript
TypeScriptサポート用 - ts-node
TypeScript用ファイルを実行するのに必要
npm i @notionhq/client
npm i blastengine
npm i dotenv
npm i typescript -D
npm i ts-node -D
ファイルの作成
今回は send.ts
というファイルを作成します。また、プロジェクト直下に .env
ファイルを作成し、環境変数を記述します。以下は .env
ファイルの内容です。
NOTION_SECRET=Notionの内部インテグレーショントークン
MAILDB_ID=NotionのデータベースページのID(URLから取得)
BLASTENGINE_USERID=blastengineのユーザー名
BLASTENGINE_APIKEY=blastengineのAPIキー
FROMNAME=送信元の名前
FROMADDRESS=送信元のメールアドレス
# 以下はフィールドの名前
STATUS_PROPERTY=送信状態
STATUS_VALUE=送信待ち
SUBJECTNAME=件名
DELIVERYID_PROPERTY=配信ID
ライブラリ読み込み
ここからは send.ts
への記述です。まず最初に必要なライブラリを読み込みます。
// blastEngine SDKとTransactionを読み込む
import { BlastEngine, Transaction } from 'blastengine';
import { Client } from '@notionhq/client';
// dotenvを読み込む
import * as dotenv from 'dotenv';
import {
PageObjectResponse,
UpdatePageResponse
} from '@notionhq/client/build/src/api-endpoints';
dotenv.config();
Notionクライアントの初期化への接続情報を作成する
.env
ファイルに定義したシークレットを使って、Notionクライアントを作成します。
// Notion APIの認証を設定
const notion = new Client({ auth: process.env.NOTION_SECRET });
blastengineの初期化
blastengine SDKを初期化します。こちらも .env
ファイルの内容を使います。
// blastEngineを初期化する
new BlastEngine(process.env.BLASTENGINE_USERID, process.env.BLASTENGINE_APIKEY);
無名関数の定義
ここからはネットワーク処理があるので、非同期処理用にasync/awaitを定義します。さらにデータベースの更新を関するので、setIntervalで処理を繰り返しています。
// メイン処理をasync関数で囲む
(async () => {
// 処理が重複されないようにするフラグ
let lock = false;
setInterval(() => {
// ここからはこの中に書いていきます
}, 5000); // 5秒ごとに実行
})();
Notionからレコードを取得
Notionを検索します。クエリーとして、メールアドレスが入力されていることと、配信対象として設定されていることを定義しています。
const ary = await fetchMail(process.env.MAILDB_ID!);
fetchMail関数は以下のようになります。関数は (async () => {
外で問題ありません。ステータスが「送信待ち」になっているデータを対象としながら、得られたデータをキーと値という簡単な形式に直しています。
// Notionデータベースから未送信の情報を取得する関数
const fetchMail = async (databaseId: string): Promise<{[key: string]: string}[]> => {
const res = await notion.databases.query({
database_id: databaseId,
filter: {
and: [{
property: process.env.STATUS_PROPERTY!,
status: {
equals: process.env.STATUS_VALUE!,
}
}]
}
});
return (res.results as PageObjectResponse[]).map(row => {
const properties = row.properties as {[key: string]: any};
const params: {[key: string]: string} = {
id: row.id,
};
Object.keys(properties).forEach(key => {
const property = properties[key];
if (property.type === 'rich_text' && property.rich_text.length > 0) {
params[key] = property.rich_text[0].plain_text;
}
if (property.type === 'title' && property.title.length > 0) {
params[key] = property.title[0].plain_text;
}
});
return params;
});
};
ロック
もし送信先リストがあるならば、解除するまでは処理をロックします。
// 処理をロック
if (ary.length > 0 && !lock) {
lock = true;
} else {
// ロックされていれば処理は中断
return;
}
メールアドレス分、処理を繰り返す
一度に複数のメールアドレスが取れる場合がありますので、その場合は処理を繰り返します。
for (const row of ary) {
// ここからはこの中に記述します
}
メール本文の読み込み
メール本文を取得します。
const body = await getBody(row.id);
getBody
関数は以下のように定義しています。Notionではページ本文は別途取得する必要があります。この関数は (async () => {
外で問題ありません。
// Notionページから本文を取得する関数
const getBody = async (pageId: string): Promise<string> => {
const { results } = await notion.blocks.children.list({
block_id: pageId,
page_size: 200,
});
const ary = (results as any[]).map(row =>
row.paragraph.rich_text && row.paragraph.rich_text[0]
? row.paragraph.rich_text[0].plain_text
: ''
);
return ary.join("\n");
};
トランザクションメールオブジェクトの作成
blastengineのトランザクションメールオブジェクトを作成します。これは即時配信用のオブジェクトです。
// Transactionオブジェクトを作成する
const transaction = new Transaction();
作成したトランザクションメールオブジェクトに、件名や送信元情報などを適用します。最後に send
メソッドを実行すれば、メールが送信されます。
await transaction
.setFrom(
process.env.FROMADDRESS!,
process.env.FROMNAME!
) // 送信元のメールアドレスと名前
.setSubject(
row[process.env.SUBJECTNAME!]
) // 件名
.setText(body) // 本文
.setTo(
row[process.env.EMAIL_PROPERTY!]
)
.send();
配信が完了すれば、配信ID(デリバリーID)が付与されています。
// 送信が完了したことをログに出力する
console.log(`送信完了${transaction.delivery_id}`);
Notionに結果を反映
メール送信が完了したら、Notionデータベースのステータスを更新します。また、配信IDも記録しておきます。
await updatePage(row.id, transaction.delivery_id!);
updatePage
関数の内容は次の通りです。
// Notionデータベースを更新する
const updatePage = async (
pageId: string,
deliveryId: number
): Promise<UpdatePageResponse> => {
return notion.pages.update({
page_id: pageId,
properties: {
[process.env.STATUS_PROPERTY!]: {
status: {
name: '送信完了'
}
},
[process.env.DELIVERYID_PROPERTY!]: {
number: deliveryId
}
}
});
};
これでメール送信が成功すると、Notionデータベースのステータスと配信IDの項目が更新されます。
スクリプトの全体像
send.ts
の内容は以下の通りです。
// blastEngine SDKとTransactionを読み込む
import { BlastEngine, Transaction } from 'blastengine';
import { Client } from '@notionhq/client';
// dotenvを読み込む
import * as dotenv from 'dotenv';
import {
PageObjectResponse,
UpdatePageResponse
} from '@notionhq/client/build/src/api-endpoints';
dotenv.config();
// Notion APIの認証を設定
const notion = new Client({ auth: process.env.NOTION_SECRET });
// blastEngineを初期化する
new BlastEngine(process.env.BLASTENGINE_USERID!, process.env.BLASTENGINE_APIKEY!);
// Notionデータベースから未送信の情報を取得する関数
const fetchMail = async (databaseId: string): Promise<{[key: string]: string}[]> => {
const res = await notion.databases.query({
database_id: databaseId,
filter: {
and: [{
property: process.env.STATUS_PROPERTY!,
status: {
equals: process.env.STATUS_VALUE!,
}
}]
}
});
return (res.results as PageObjectResponse[]).map(row => {
const properties = row.properties as {[key: string]: any};
const params: {[key: string]: string} = {
id: row.id,
};
Object.keys(properties).forEach(key => {
const property = properties[key];
if (property.type === 'rich_text' && property.rich_text.length > 0) {
params[key] = property.rich_text[0].plain_text;
}
if (property.type === 'title' && property.title.length > 0) {
params[key] = property.title[0].plain_text;
}
});
return params;
});
};
// Notionデータベースを更新する
const updatePage = async (
pageId: string,
deliveryId: number
): Promise<UpdatePageResponse> => {
return notion.pages.update({
page_id: pageId,
properties: {
[process.env.STATUS_PROPERTY!]: {
status: {
name: '送信完了'
}
},
[process.env.DELIVERYID_PROPERTY!]: {
number: deliveryId
}
}
});
};
// Notionページから本文を取得する関数
const getBody = async (pageId: string): Promise<string> => {
const { results } = await notion.blocks.children.list({
block_id: pageId,
page_size: 200,
});
const ary = (results as any[]).map(row =>
row.paragraph.rich_text && row.paragraph.rich_text[0]
? row.paragraph.rich_text[0].plain_text
: ''
);
return ary.join("\n");
};
(async () => {
let lock = false;
setInterval(async () => {
const ary = await fetchMail(process.env.MAILDB_ID!);
// 処理をロック
if (ary.length > 0 && !lock) {
lock = true;
} else {
// ロックされていれば処理は中断
return;
}
for (const row of ary) {
const body = await getBody(row.id);
const transaction = new Transaction();
await transaction
.setFrom(
process.env.FROMADDRESS!,
process.env.FROMNAME!
) // 送信元のメールアドレスと名前
.setSubject(
row[process.env.SUBJECTNAME!]
) // 件名
.setText(body) // 本文
.setTo(
row[process.env.EMAIL_PROPERTY!]
)
.send();
console.log(`送信完了${transaction.delivery_id}`);
await updatePage(row.id, transaction.delivery_id!);
}
lock = false;
}, 5000);
})();
実行する
以下のようにしてコマンドプロンプトやターミナルで実行します。
npx ts-node send.ts
そしてデータベースに新しい行を追加して配信先や件名を入力し、最後にステータスを配信待ちに指定します。そうすると、以下のようなメッセージが流れるはずです。
送信完了1326
送信完了1327
まとめ
今回はNotionのデータベースからメールアドレス情報を取得して、blastengineで配信するまでの流れを紹介しました。次回は配信結果をNotionに反映する流れを解説します。