こんにちは。
弁護士ドットコム クラウドサイン事業本部の北見と申します。
CloudSign のバックエンドの開発を主に担当しています。
CloudSign では、契約書類の確認依頼や締結完了の案内を受信者や関係者向けにメールで通知する仕組みがあります。
本記事では、メールの文面を修正するにあたって効率的に内容を確認できるツールを作成した件を紹介します。
メール送信の仕組み
まずは、クラウドサインのサービスからメールを送信する処理の流れと、メール本文を構築する仕組みについてご説明します。
一部例外もありますが、クラウドサインからのメール送信は、Web サービスへのリクエストがトリガーとなります。ユーザーによる Web サービスの操作があった際に、その結果を通知する形で送信されます。
処理の流れは以下のとおりです。
- ユーザーが Web サービスへ特定の処理をリクエストする
- Web サービスはメール通知が必要な場合はバッチへメール送信を依頼する
- メール通知バッチはメールコンポーネントを用いて、SMTP 経由でメールをユーザーに送信する
このうち、メール本文の構築を処理するのはメールコンポーネントです。
この処理は Go 言語で実装されており、メール本文の構築は text/template と html/template、そのテンプレートコードの管理は embed といずれも Go 標準のパッケージを用いて実現しています。
以下は送信メールの本文を構築するコードです。
HTML メール/テキストメールそれぞれについて全体で共通となるレイアウトテンプレート(base.*.tmpl
)を読み込んだ後にメールの種別ごとに実装されたテンプレートを展開し、さらに必要な埋め込みパラメータを渡して本文を構築しています。
以下は実際に運用しているコードではなく、要所をピックアップして流れを記載したものです。
package mail import ( htmpl "html/template" ttmpl "text/template" "embed" ) var ( // 本文構築に利用するテンプレートはバイナリリソースとして埋め込まれている //go:embed tmpl/* tmpl embed.FS ) // Mail はメールの情報を保持する構造体 type Mail struct { from, to string text, html string } // BuildBody は名称で指定した区分とpに含まれるデータからメール本文を構築する // name には送信するメールの種別を指定、これは tmpl/ 配下に配置されたファイルのプレフィックスになる // p には各メールに埋め込まれるパラメータがフィールドとして保持されている func (m *Mail) BuildBody(name string, p Param) { // HTMLのbody ht, _ := htmpl.New("").ParseFS(tmpl, "base.html.tmpl") ht, _ = ht.ParseFS(tmpl, name+".html.tmpl") m.html, _ = ht.Execute(p) // テキストのbody tt, _ := ttmpl.New("").ParseFS(tmpl, "base.text.tmpl") tt, _ = tt.ParseFS(tmpl, name+".text.tmpl") m.text, _ = tt.Execute(p) }
メール送信処理の問題点
サービスを運用する中でメールの文面の修正が必要になることがありますが、以前の開発環境ではメールコンポーネント内で構築されるメール本文の内容を個別に確認する手段が用意されておらず、確認するためにはシステム上でメールが送信されるための処理を一から実行する必要がありました。
メール送信が発生する処理はユーザー登録やチームの管理情報変更や書類の状態変更など多岐に渡りますが、Web サービス上で容易に操作できるものもあれば障害時の通知メールなど通常操作では実現できないものもあり、特に後者のメール内容を確認するための手間が問題になっていました。
また、文面の修正を確認する際は、テンプレートを修正後にバッチを都度デプロイして確認する必要がありました。
文章の順番入れ替えや文言の軽微な調整の都度に作業するには負荷が大き過ぎました。
これらの課題を包括的に解決する方法として、メール本文を構築するコマンドラインツールで確認できるようにしました。
メール本文構築ツール
上記のモチベーションを元に以下のようなツールを作りました。
コンソールアプリ
ターミナルから実行するコンソールアプリとして実装し、コマンドのパラメータに従って HTML またはテキストのメール本文を標準出力に書き込むようにしました。
$ go run . [-p <param>] <category> <html|text>
具体的な使用例は以下の通りです。
# 締結完了通知(complete)メールのテキスト本文を生成する $ go run . complete text テスト花子様 テスト太郎様が送信された...
HTML メールの場合も標準出力に出てくるので、これをファイルに保存してビューアー上に表示させることで内容を確認できるようになります。
# 書類確認依頼(receive)メールのHTML本文を生成する # 埋め込むパラメータは受信者を想定した内容(receiver)に切り替える $ go run . -p receiver receive html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ...
上記のコマンドでは -p
でパラメータとして埋め込むサンプルのデータを指定しています。
これは送信するメールの種別によって必要なデータの項目が異なるためで、ツール内では map でいくつかのサンプルパラメータを用意していてそれをオプション引数で指定できるようにしています。
またコンソールアプリとして利用する場合、通常は go install
などでバイナリ化して実行する形式で利用するケースが多いですが、このツールは埋め込みリソースとして定義された内容をローカルで編集した際の確認も目的に含んでいるため、あえてビルドしないで go run .
で利用するツールにしました。
Workspaces モード を前提に動作する
修正した内容を直接参照したかったため、Workspaces モードを有効化して動かすツールにしました。
Go には Go Modules という外部のモジュールを利用する仕組みがあり、go.mod
のファイルに記述された外部モジュールを利用できますが、これはリモートのリポジトリを参照しているため、ローカルで修正した内容をすぐに反映させることができません。
Workspaces モード(multi-module workspaces)は Go1.18 から導入された仕組みで、go.mod
とは別に go.work
のファイルを作成してローカルに配置したモジュールを記述して、ローカルモジュールを参照させることができます。
※正確な情報は公式ドキュメントや他にも該当機能を説明する記事がありますのでそちらを参照してください。
本来は開発中にモジュール間の参照を一時的にローカルのファイルで解決させるための機能となりますが、このツールではそもそもローカルで変更されたファイル内容の確認を目的としているので、Workspaces モードの利用を前提にしています。
この仕組みを用いると、埋め込みリソースとして管理されるメールテンプレートをローカルで修正した後も Go のモジュールバージョンの管理を都度変更しないで内容を確認できるようになります。
Before / After
このツールを導入した結果、作業の手間と時間が大幅に削減されました。
導入前
- メールの文面や埋め込みパラメータを修正する(数秒〜数分)
- 修正内容を Git に commit してから push する(1 分)
- ローカルのバッチをビルドしなおす(1 分〜3 分)
- 主にメールを含むコンポーネント群のリポジトリの再ダウンロードに時間がかかる
- ローカルの Web サービスを操作してメールを送信する(1 分〜5 分)
- 物によっては事前に Web サービスの実装を部分的に修正してデプロイし直す(5 分〜)
- 送信されたメールを確認する(数秒〜2 分)
導入後
- メールの文面や埋め込みパラメータを修正する(数秒〜数分)
- ツールを実行する(数秒〜1 分)
- 出力された内容を確認する(数秒〜2 分)
- HTML の場合はプレビューを表示する(数秒)
ここで注意したいのが、 メールの文面や埋め込みパラメータを 1 箇所でも変更する都度 このサイクルが必要になる、ということです。 細かい文面の調整を何度も繰り返しているとこの作業だけで数時間〜 1 日以上溶かしていたところ、同作業を数分〜数十分で対応できるようになったため、気軽に結果の確認が行えるようになりました。
今後の展望
このツールを導入してメールの確認が行いやすくなりましたが、まだ改善点もあります。
テストデータの拡充
送信されるメールに埋め込むためのテンプレートやパラメータデータはメールの種別ごとに異なります。
テンプレートについては embed
で埋め込まれているものを使うのであらためての追加は不要ですが、サンプルとして埋め込むパラメータは確認したいメールに応じて追加・修正する必要があります。
# サインアップ用のパラメータを追加したり $ go run . -p newuser signup html # チームに招待されたユーザー用のパラメータを追加したり $ go run . -p invited-user invite html
このツールはいわゆるテストツールなので、サービスの仕様を網羅してテストデータを拡充させることが重要ですが、今は直近のプロジェクトで扱った一部のメールのみしか用意していません。
要はテストデータが足りていないのですが、作業コストを削減するために作成したツールでテストデータを端から用意しようとするとそれだけで相当なコストが必要です。
そのため今回は目の前で欲しい分のデータのみを用意し、今回作成したツールを社内に周知して利用してもらいつつ、
今後のサービス改修に応じて開発者に随時テストデータを追加してもらう方針といたしました。
(README にも適宜いじってコミットしてください、とアナウンスを記載しています)
HTMLプレビューの簡易化
以前のように一通りのオペレーションを操作しなくてもメールが確認できるようになったのは良いのですが、それでも HTML メールの確認をしようとすると、HTML ソースをファイルへ保存した後にファイルをプレビューで表示するための作業が必要です。
文字に起こすと大した手間ではなさそうですが、vscode のプレビュー拡張のホットリロードうまく動かなくて毎回ビューを開き直したりしていたので、何度も繰り返し操作していてると手間というよりはテンポが悪くてもどかしくなってきます。
この問題については、例えばフロントとしてブラウザから操作できる Web サービスを立てて、そこから引数に当たる入力を指定した結果としてバックで今回のツールを動かした結果をブラウザで表示する、のような構成にすることで対応できそうな気がしています。
ただ 手間を省くために用意したツールなのにそこまで構成を広げてまで対応するべきか という点を悩んでいるところもあるので、ひとまず他の人がツールを利用した際にその手の不満が上がってきてからあらためて検討しようかと考えています。
おわりに
今回、Workspaces モードをツールの運用に組み込んだ例と、開発中のメールの手元での確認が大変だったところから、あまり手間をかけないで工数を削減するためにツールを作った話を紹介しました。
このような施策を進めると、当社ではよくプログラマの三大美徳のうちの「怠惰」に関する話題が上がることがあります。
自分が感じる作業の手間は他の人も同様に感じることがあるので、その辺りを巻き込みながらちょっとずつ改善を進めていくと、気づいたときにはいろいろと痒いところに手が届く体制になることもあります。
最後まで読んでいただき、ありがとうございました。