SMTP・API連携で高速メール配信するならブラストエンジン

Notionのデータベースにレコードが追加されたらメールを送信する

2022年11月6日 Notion

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

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

機能について

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

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

ユーザ登録する

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

Untitled.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

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

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

データベースを作成する

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

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

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

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

Notionのインテグレーションページにて、新しいインテグレーションを作成します。今回は blastengine としています。名前やワークスペースを選択すればできあがりです。

Untitled 1.png

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

Untitled 2.png

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

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

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

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

// 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に反映する流れを解説します。

エンジニア向けメール配信システム「ブラストエンジン(blastengine)」

blastengine(プラストエンジン)ロゴ

エンジニアを面倒なメールに関するトラブルから解放するために作られたブラストエンジン
まずは無料トライアルで、その使いやすさを実感してみませんか?

\メールアドレス入力のみ/

無料トライアル