Notionのデータベースと連携してblastengineからメール送信を行う

# Notionからメールアドレスを取得する


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

今回はそんなNotion内にあるメールアドレスを使って、blastengineと組み合わせてみました。

# 機能について

- データベースにある顧客リストのメールアドレス宛にメールを一括送信
- エラーリストを使って次回以降送信しないようにNotionのデータを更新

前半となるこの記事ではデータベースにある顧客リストのメールアドレス宛にメールを一括送信について実装します。

## ユーザ登録する

blastengineにユーザ登録します。管理画面に入るためのユーザID、パスワードが手に入るので、[ログイン](https://blastengine.jp/)又は無料トライアルからアカウントをさくせします（ユーザIDは後で使います）。
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/d1aa1999-ead8-2771-4730-41b0e00e73a2.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)

# Notionと連携するNoway Formを使う

まずNotionのデータベースにメールアドレスを登録する仕組みを構築します。今回は[Noway Form](https://www.noway-form.com/ja)を利用しています。Noway Formはお問い合わせフォームを作成できるサービスで、そのデータをNotionのデータベースに保存できます。

![Untitled 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/44f766d1-2271-41f5-d62f-f0351ab3a019.png)

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

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

| 項目名 | 型 |
| --- | --- |
| 会社名 | テキスト |
| 名前 | テキスト |
| メールアドレス | メールアドレス |
| お問い合わせ内容 | テキスト |
| 送信不可 | チェックボックス |

送信不可はデフォルトチェックなしなので、フォームから入力した段階では全データが送信対象になります。

## Noway Formと連携する

Noway FormのWebサイトからNotionの連携と、フォーム設計を進めていきます。


![Untitled.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/f0ec4e67-0e77-9d35-4006-3e008977a51e.jpeg)


完了したら一度テストで登録してみると良いでしょう。無事データベースにデータが追加できていれば完了です。


![Untitled 1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/61e9a133-d656-30da-0dfb-a74cc7ecf555.jpeg)


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

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

![Untitled 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/24e58411-98a2-5c8a-3fba-fe7d072e3b0e.png)

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

![Untitled 3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/7c789603-d662-a551-f94b-5654c2f3dc0b.png)
### ページを作る

メールの件名、本文に利用するNotionページを作成します。ページのタイトルがメールの件名、ページの内容がメール本文になります。

![Untitled 4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/58b9206e-886c-04d6-50d5-e8af81648810.png)
### インテグレーションを追加する

先ほど追加したデータベース、メール本文用のページに対してNotionインテグレーションを追加します。

右上にある三点リーダーよりコネクトの追加を選択し、作成したインテグレーション（今回は `blastengine` を追加します）。
![Untitled 5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/0d576b2a-cdcb-bf81-85b2-e1141a7a2c55.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 @kintone/kintone-js-sdk
npm i blastengine
npm i dotenv
npm i typescript -D
npm i ts-node -D
```

## ファイルの作成

今回は `mail.ts` というファイルを作成します。また、プロジェクト直下に `.env` ファイルを作成し、環境変数を記述します。以下は `.env` ファイルの内容です。

```
NOTION_SECRET=Notionの内部インテグレーショントークン
DB_ID=NotionのデータベースページのID（URLから取得）
CHECKBOX_ID=送信不可フラグのID
EMAIL_PROPERTY=メールアドレスの入っているフィールドの名前
NAME_PROPERTY=名前が入っているフィールドの名前
PAGE_ID=NotionのメールテンプレートページのID（URLから取得）

BLASTENGINE_USERID=blastengineのユーザー名
BLASTENGINE_APIKEY=blastengineのAPIキー
FROMNAME=送信元の名前
FROMADDRESS=送信元のメールアドレス
```

### 送信不可フラグのID

これは検索時に利用するのですが、これはフィルターを実行してみた結果から取得しました（開発者ツールにて）。データベースのプロパティを調べて取得するようにすると、もう少し簡単かも知れません。筆者の例で言えば、Kcrxになります。
![Untitled 6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982596/17ad1572-0ee0-d067-dca1-4cb44772a4df.png)
名前やメールアドレスのフィールドの名前は日本語のものでOKです。

## ライブラリ読み込み

ここからは `mail.ts` への記述です。まず最初に必要なライブラリを読み込みます。

```jsx
// blastEngine SDKとBulkを読み込む
import { BlastEngine, Bulk } from 'blastengine';
import { Client } from '@notionhq/client';

// dotenvを読み込む
import * as dotenv from 'dotenv';
import { PageObjectResponse } 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を定義します。

```jsx
// メイン処理をasync関数で囲む
(async () => {
  // ここからはこの中に書いていきます
})();
```

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

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

```jsx
const ary = await fetchAll(process.env.DB_ID!);
```

fetchAll関数は以下のようになります。関数は `(async () => {` 外で問題ありません。

```jsx
// Notionデータベースから未送信の情報を取得する関数
const fetchAll = async (databaseId: string): Promise<{email: string, name: string}[]> => {
	const res = await notion.databases.query({
    database_id: databaseId,
		filter: {
			and: [{
				property: process.env.CHECKBOX_ID!,
				checkbox: {
					equals: false,
				}
			}]
		}
	});
	return (res.results as PageObjectResponse[]).map(row => {
		const properties = row.properties as {[key: string]: any};
		const name = (properties[process.env.NAME_PROPERTY!].title[0].plain_text);
		const email = properties[process.env.EMAIL_PROPERTY!].email;
		return {
			name, email,
		}
	});
};
```

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

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

```jsx
const { title, body } = await getTemplate(process.env.PAGE_ID!);
```

`getTemplate` 関数は以下のように定義しています。Notionではページタイトルと本文は別途作成するので、以下のように `getTitle` と `getBody` を別で呼び出しています。これらの関数は `(async () => {` 外で問題ありません。

```jsx
// Notionページからテンプレートのタイトルと本文を取得する関数
const getTemplate = async (pageId: string): Promise<{title: string, body: string}> => {
	const title = await getTitle(pageId);
	const body = await getBody(pageId);
	return { title, body };
}

// Notionページからタイトルを取得する関数
const getTitle = async (pageId: string): Promise<string> => {
	const res = await notion.pages.retrieve({ page_id: pageId }) as any;
	return res.properties.title.title[0].plain_text;
};

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

メールの内容は以下のようにしています。 `__name__` は配信先毎に置き換えられる文字列です。

```
__name__さん、こんにちは

これはメールの本文です。
```

### バルクメールオブジェクトの作成

blastengineのバルクメールオブジェクトを作成します。これは一括配信用のオブジェクトです。

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

作成したバルクメールオブジェクトに、件名や送信元情報などを適用します。

```jsx
// Bulkオブジェクトにメールの情報を設定する
await bulk
	.setFrom(process.env.FROMADDRESS, process.env.FROMNAME)   // 送信元のメールアドレスと名前
	.setSubject(process.env.SUBJECT)   // 件名
	.setText(text)   // 本文
	.register();   // 送信するメールの情報をblastEngineに登録する
```

### Notionのレコード情報を配信先として設定

先ほど取得したNotionのレコードから、メールアドレスと名前を使って配信先として登録します。1つ目の引数がメールアドレス、2つ目の引数は置き換え文字列（メール本文で指定した `__name__` と置き換えられます）になります。

```jsx
// レコードを処理し、Bulkオブジェクトに宛先を追加する
ary.map(({email, name }) => {
	bulk.addTo(email, { name });
});
```

### 宛先情報を反映

登録した宛先情報をblastengineに反映します。

```jsx
// blastEngineに登録したメールの情報を更新する
await bulk.update();
```

### 送信する

後はメールを送信するだけです。引数として、配信日時も指定できます。

```jsx
// blastEngineを使ってメールを送信する
await bulk.send();
```

配信が完了すれば、配信ID（デリバリーID）が付与されています。

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

## スクリプトの全体像

`mail.ｔs` の内容は以下の通りです。

```jsx
// blastEngine SDKとBulkを読み込む
import { BlastEngine, Bulk } from 'blastengine';
import { Client } from '@notionhq/client';

// dotenvを読み込む
import * as dotenv from 'dotenv';
import { PageObjectResponse } 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 fetchAll = async (databaseId: string): Promise<{email: string, name: string}[]> => {
	const res = await notion.databases.query({
    database_id: databaseId,
		filter: {
			and: [{
				property: process.env.CHECKBOX_ID!,
				checkbox: {
					equals: false,
				}
			}]
		}
	});
	return (res.results as PageObjectResponse[]).map(row => {
		const properties = row.properties as {[key: string]: any};
		const name = (properties[process.env.NAME_PROPERTY!].title[0].plain_text);
		const email = properties[process.env.EMAIL_PROPERTY!].email;
		return {
			name, email,
		}
	});
};

// Notionページからテンプレートのタイトルと本文を取得する関数
const getTemplate = async (pageId: string): Promise<{title: string, body: string}> => {
	const title = await getTitle(pageId);
	const body = await getBody(pageId);
	return { title, body };
}

// Notionページからタイトルを取得する関数
const getTitle = async (pageId: string): Promise<string> => {
	const res = await notion.pages.retrieve({ page_id: pageId }) as any;
	return res.properties.title.title[0].plain_text;
};

// 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 () => {
	const ary = await fetchAll(process.env.DB_ID!);
	const { title, body } = await getTemplate(process.env.PAGE_ID!);
	const bulk = new Bulk();

	// Bulkオブジェクトにメールの情報を設定する
	await bulk
		.setFrom(process.env.FROMADDRESS!, process.env.FROMNAME)   // 送信元のメールアドレスと名前
		.setSubject(title)   // 件名
		.setText(body)   // 本文
		.register();   // 送信するメールの情報をblastEngineに登録する
	// レコードを処理し、Bulkオブジェクトに宛先を追加する
	ary.map(({email, name }) => {
		bulk.addTo(email, { name });
	});
	
	// Bulkオブジェクトを更新する
	await bulk.update();
	
	// Bulkオブジェクトでメールを送信する
	await bulk.send();
	
	// 送信完了メッセージを表示する
	console.log(`送信しました ${bulk.delivery_id}`);
})();
```

## 実行する

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

```jsx
npx ts-node mail.ts
```

以下のようなメッセージが出れば配信されているはずです。

```jsx
送信しました 105
```

## まとめ

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

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