#Notionのデータベースに追加されたらメールを送信する。



Notionは最近注目度の高い情報共有サービスです。Wikiのように自由テキストを保存したり、データベースのように構造化されたデータを保存もできます。

今回はそんなNotion内にあるメールアドレスを使って、blastengineと組み合わせてみました。データベースのデータ追加をモニタリングし、そのデータを使ってメール送信します。

# 機能について

- データベースにあるデータを使ってメール送信・ステータスの更新
- バウンス情報をNotionのデータに反映

前半となるこの記事ではデータベースにあるデータを使ったメール送信・ステータスの更新について実装します。

## ユーザ登録する

blastengineにユーザ登録します。管理画面に入るためのユーザID、パスワードが手に入るので、ログインします（ユーザIDは後で使います）。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/81c24673-b7f9-3a5c-e179-fcd58857f235.png)

## 送信元ドメインのSPFを設定する

送信元として利用するドメイン（自分で持っているもの）の設定をします。これは任意のドメイン管理サービスで設定できますが、TXTレコードに以下のSPFを追加します。

```
txt @ v=spf1 include:spf.besender.jp ~all
```

## APIキーを取得する

ログイン後、管理画面の右上にある設定メニューに移動します。

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/47b2b2ac-2e41-77fe-d84f-0ae0b7541f36.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/47b2b2ac-2e41-77fe-d84f-0ae0b7541f36.jpeg)

そして設定の中で、APIキーを取得します。

![https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/2b5ca124-3020-1d39-84e4-911cd4db27e2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/2b5ca124-3020-1d39-84e4-911cd4db27e2.png)

## データベースを作成する

作成するデータベースでは、お問い合わせフォームに必要な項目を登録しておく必要があります。今回は以下の項目を作成しました。

| 項目名 | 型 |
| --- | --- |
| 会社名 | テキスト |
| 名前 | テキスト（タイトル） |
| メールアドレス | メールアドレス |
| 件名 | テキスト |
| 送信状態 | ステータス |
| バウンス | テキスト |
| 配信ID | 数字 |
| エラーメッセージ | テキスト |

送信状態は「作成中」「送信待ち」「送信完了」の3つのステータスがあります。

## Notionインテグレーションを作る

[Notionのインテグレーションページ](https://www.notion.so/my-integrations)にて、新しいインテグレーションを作成します。今回は `blastengine` としています。名前やワークスペースを選択すればできあがりです。

![Untitled 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/9f13c88b-24a1-0b2b-2168-ef98330d024e.png)


**`内部インテグレーショントークン`** というのが生成されるので、メモしておきます。

![Untitled 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/a446b66c-5849-3f43-7045-f2239eb6d7c7.png)

### インテグレーションを追加する

先ほど追加したデータベースに対してNotionインテグレーションを追加します。

右上にある三点リーダーよりコネクトの追加を選択し、作成したインテグレーション（今回は `blastengine` を追加します）。

![Untitled 3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/7d752716-eed1-f7c9-c44f-cd17bf42b966.png)

# アプリを作る

今回は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` への記述です。まず最初に必要なライブラリを読み込みます。

```jsx
// 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クライアントを作成します。

```jsx
// Notion APIの認証を設定
const notion = new Client({ auth: process.env.NOTION_SECRET });
```

## blastengineの初期化

blastengine SDKを初期化します。こちらも `.env` ファイルの内容を使います。

```jsx
// blastEngineを初期化する
new BlastEngine(process.env.BLASTENGINE_USERID, process.env.BLASTENGINE_APIKEY);
```

## 無名関数の定義

ここからはネットワーク処理があるので、非同期処理用にasync/awaitを定義します。さらにデータベースの更新を関するので、setIntervalで処理を繰り返しています。

```jsx
// メイン処理をasync関数で囲む
(async () => {
  // 処理が重複されないようにするフラグ
	let lock = false;
  setInterval(() => {
    // ここからはこの中に書いていきます
  }, 5000); // 5秒ごとに実行
})();
```

### Notionからレコードを取得

Notionを検索します。クエリーとして、メールアドレスが入力されていることと、配信対象として設定されていることを定義しています。

```jsx
const ary = await fetchMail(process.env.MAILDB_ID!);
```

fetchMail関数は以下のようになります。関数は `(async () => {` 外で問題ありません。ステータスが「送信待ち」になっているデータを対象としながら、得られたデータをキーと値という簡単な形式に直しています。

```jsx
// 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;
	});
};
```

## ロック

もし送信先リストがあるならば、解除するまでは処理をロックします。

```java
// 処理をロック
if (ary.length > 0 && !lock) {
	lock = true;
} else {
	// ロックされていれば処理は中断
	return;
}
```

## メールアドレス分、処理を繰り返す

一度に複数のメールアドレスが取れる場合がありますので、その場合は処理を繰り返します。

```java
for (const row of ary) {
  // ここからはこの中に記述します
}
```

### メール本文の読み込み

メール本文を取得します。

```jsx
const body = await getBody(row.id);
```

`getBody` 関数は以下のように定義しています。Notionではページ本文は別途取得する必要があります。この関数は `(async () => {` 外で問題ありません。

```jsx
// 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のトランザクションメールオブジェクトを作成します。これは即時配信用のオブジェクトです。

```jsx
// Transactionオブジェクトを作成する
const transaction = new Transaction();
```

作成したトランザクションメールオブジェクトに、件名や送信元情報などを適用します。最後に `send` メソッドを実行すれば、メールが送信されます。

```jsx
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）が付与されています。

```jsx
// 送信が完了したことをログに出力する
console.log(`送信完了${transaction.delivery_id}`);
```

## Notionに結果を反映

メール送信が完了したら、Notionデータベースのステータスを更新します。また、配信IDも記録しておきます。

```jsx
await updatePage(row.id, transaction.delivery_id!);
```

`updatePage` 関数の内容は次の通りです。

```jsx
// 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.ｔs` の内容は以下の通りです。

```jsx
// 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);
})();
```

## 実行する

以下のようにしてコマンドプロンプトやターミナルで実行します。

```jsx
npx ts-node send.ts
```

そしてデータベースに新しい行を追加して配信先や件名を入力し、最後にステータスを配信待ちに指定します。そうすると、以下のようなメッセージが流れるはずです。

```jsx
送信完了1326
送信完了1327
```

## まとめ

今回はNotionのデータベースからメールアドレス情報を取得して、blastengineで配信するまでの流れを紹介しました。次回は配信結果をNotionに反映する流れを解説します。

[エンジニア向けメール配信システム「ブラストエンジン（blastengine）」](https://blastengine.jp/)