受信メールを添付ファイルごとTypetalkで受け取る仕組みで「メールとチャットの溝」を埋める[10日目]

Pocket
LINEで送る

この記事はShiftallのプロジェクトに関わるメンバーが日替わりでブログを更新していくアドベントカレンダー企画の10日目です。その他の記事はこちらのリンクからご覧下さい。

アドベントカレンダー2018
https://blog.shiftall.net/ja/archives/tag/adventcalendar2018/

まつけんです。今回は、担当する業務範囲の中で、私個人としては、心のオアシスである社内SE業としてつくったツールを紹介してみようと思います。

どういうツールか

弊社の社内のコミュニケーションは、昨今では常識とも言えるチャットを導入しています。採用しているサービスは「Typetalk」です。なぜTypetalkかというのは、弊社ブログでも言及している、Shiftallが社内共有に選んだチャットサービスを参照していただくとして、今回はそれと組み合わせて使うツール(サービス?)です。

社内コミュニケーションとしてのチャットは弊社では完全に定着をしています、社内でメール等別のツールを使うケースは皆無です。しかし、社外の方とのコミュニケーションとなると、やはりそうもいきません。どうしてもそちらはメールでのやりとりが中心となります。

この社外の人とのメールという存在がなかなか厄介で、メールでやりとりをした結果をチャットに転記したりという形で共有するメンバーも多くいるのですが、どうしてもチャットへの転記を忘れたり、情報が断片的になってしまったりします。一番徒労感があるのが、あるファイルを探すときチャットをいくら検索しても見つけられず、よくよく思い出すとメールに添付されていただけでそちらを検索してやっと見つけるなんて顛末です。このように、どうしてもメールとチャットの間に溝があるというのは、チャットが定着している会社ほど起こることではないでしょうか。これをちょっとでも埋められるといいなと思ってつくったツールです。

というわけで、実際に用意するものは単純です。特定のメールアドレスをCcなりで宛先に入れることによって、そのメールアドレスにしたがって対象トピックを決め、メール内容をそれなりに読みやすくして投稿します。添付ファイルもチャット上でファイルとしてアップロードされ、メールのスレッドもチャット上の返信として再現します。こうすることで、メールでのやりとりを社内に意識することなく共有することが可能になります。これをどうやって実現したかというのが今回の主旨です。

どんな構成にするか

次は、実際にどのように考え、どのように作ったかという過程の紹介です。社内SE業とはいうものの、小さな会社で私ひとりで作るモノですし、正直、私のささやかな楽しみという側面が大きいので、あまり時間をかけず、楽しみながらやれるようにというのが発想の出発点です。そのため、趣味が大きく反映されるのは致し方ないところです。(ただ、弊社のスタンスとして、開発者がフレンドリーであるという点を、技術などの選定の基準に加えるのはアリだという部分もあり、通常の業務でも個人の趣味というと聞こえは悪いですが、ほぼ自由に言語、ツール、サービスを選べるようにしています。)

今回は、この後、軽く経緯を紹介しますが、結局、既存サービスの組み合わせではなく、Mailgunというメール(SMTPを含む)を扱えるサービスとGoogle App Engineをつかって作ることで実現しています。

すごく簡単な構成としてはこういう形です。

この構成では、メール → (SMTP) → Mailgun →(HTTP)→ GAE/Python37 →(HTTP)→ Typetalkとデータが流れていきます。

この構成になるまで、自分の中では試行錯誤と紆余曲折がありました。とはいえ、このどういうツール、サービス、技術を組み合わせて実現するかというのを考えるのがもっとも楽しく、旅行は計画を立てるのが一番楽しいというのに通ずるなと思ったりしています。

まず最初の候補は、この手のメールをチャットに投稿したいという需要はそれなりにあるらしく、Typetalkも公式ブログで、Zapierと連携して書き込む方法を紹介しています。これを最初試したわけですが、メール本文がほぼそのままメッセージとして投稿されるので、個人的な趣味に近いのですが、正直、読みづらいなぁというところでした。こういったボットの投稿は、パッと見た目で判別できることで、脳内フィルターで読み飛ばすという行為ができるのは重要だと思っています(逆に、SlackのEmail integrationはメールが常に折りたたまれて投稿されて、件名と最初の一行目が薄くみえるだけとなっていて、個人的には読む気がほぼ起こらないです。そういう意味で、どういった整形をして投稿するかは重要だと思っています)。

その他、気になったポイントは、

  • 添付ファイルを扱うのが、ZapierのwebhookでのActionで設定するアプローチでは、Typetalk APIとの相性的に難しそうな印象だったこと
  • 宛先にあわせてトピックを切り替えるためにはそのたびにZapを作らなければならないこと
  • そして、こういったサービスを使うのが自分があまり得意でないこと

というあたりで早々に諦めました。

この辺で既存サービスを組み合わせてコードを書かずになんとかするアプローチを諦め、手間をかけない範囲で自作することになるわけですが、サーバの運用保守なんてもちろんやりたくないので、ある程度手放しで勝手に動いてくれそうな範囲で構成を考えます。

まず思いついたのは、AWS SESでメール受信+Lambdaで処理して投稿、という至極ありがちな構成です。AWS自体は一番慣れていますし、まずはこれで試しながら作り始めました。

とりあえず、ざっと作ってみるかということで、SESでDNSなどメールが受信できるように設定をし、Lambda Functionが起動するように、というところまで作って気づいたのですが、SESでメールを受信してLambdaが起動されるときに渡されるパラメータにはメール本文はありません。これはまあ言われればそれはそうで、添付ファイルなどがあれば全体容量は容易に数十MBになるので、そんなものを直接渡すという発想にならないのは至極当然です。そこまで調べずに作りはじめてしまったわけですが。

これに対応するのは簡単で、ググればよくある例のとおり、まずはメール本体はS3に保存し、SESもしくはS3からLambdaを起動する方法です。そして、その処理の中で、S3のURLにアクセスしてメール本文を読み込んで様々な処理をします。

ただ、S3に保存されるのは、SMTPで渡されるメールそのものです。当然MIMEエンコードされていますし、添付ファイルやhtmlメールはmultipartになっていて、本文やファイルなどは自力で取り出してよしなにやらねばなりません。今回はPythonで書くつもりだったので、標準ライブラリのemailでそこまで難しくなく処理できます。が、どこまでのメール(文字エンコードどうするんだっけとか、添付ファイルのMIME-Type推定やHTMLメールを含めて、なに本文とするか等)をうまく処理できるのかというのを考え始めるとなかなか厄介です。1-2日でとりあえず動かしはじめたいレベルにはすこし荷が重い感じがしてきました。

あと、それ以上に厄介なのが、基本的に運用は一切かんがえたくない&できれば格安の料金でやりたい、という前提での、S3に置かれたメールの削除です。メール処理中のエラー処理に失敗したりなどで、確実に削除できなければ、徐々にメールがたまり、S3でそこそこの課金をされてしまうんじゃないかなというのが気になりもしました。(そのときは思いつきませんでしたが、よくかんがえればこの問題はS3のライフサイクルで数日経ったら削除、というのを設定しておけばよかったですね。つくっているときはそこまで思い至りませんでした。)

このあたりまで試して分かったところで、雑談中に教えてもらったのがMailgunです。トップページなどを見ると、分かるとおり、Developer friendlyで分かりやすくシンプルなAPIを提供しており、MailgunRoutesという機能でメールを受信して、HTTPのリクエストに変換して任意のURLにPOSTしてくれます。これを使うと様々な利点がありそうです。

  • 添付ファイルの抽出処理、文字コードやMIMEエンコードに関わる変換処理などは隠蔽され、HTTPで渡される文字列はすべてUTF-8になる、添付ファイルも示されたURLをただGETすればよくなるので深く考えなくてよいこと
  • 不要そうな引用やHTMLメールからそれっぽい部分を抜き出したりして、読むと良さそうな本文っぽいところを抽出してくれること(実際やってみるとうまく動かないケースもちょこちょこありますが、それなりにいい感じです)
  • スパムメールフィルターを通してくれること
  • Routesのstore()というルールで定義するとメール本文や添付ファイルが保存されたURLが生成されるが、このURLの有効期限は3日間。深く考えずとも勝手に削除されるのが今回はフィットすること

というわけで、SES+Lambda Functionから、Mailgun+GAEに乗り替えることにしました。ちなみに、Mailgunに組み合わせるのがなぜGAEかと言われると単なる趣味です。ここまできて、Lambdaを使うのも芸がないし、ということで、いわゆるサーバレス的なものの中で親しみがあり、無料範囲でどうにかなりそうということで、今回はGAE/Python3を選びました。まだβですが、第二世代のStandard環境ということで、GAE依存する特殊な制限がほぼなくなり、普通にPythonのWebフレームワーク(今回はFlaskにしました。)でほぼそのまま作れば良い、というのも好感触です。deployも簡単ですし、requirements.txtもそのまま使ってくれます。

できあがったもの

実際のコードはコチラ。数日で時間を見つけて書いたので、とりあえず作ってみた感が強く、ユニットテストやコメントなどは皆無ですが、二週間ほど運用してありがちなエラーを潰したので、最近は日々、元気に動いてくれています。

コードの中身は解説するとこのブログ1回での文章量を遙かに超えそうですし、ぶっちゃけそれほど複雑なことはしていないので、てきとうに読んでいただくとして(実際、ほぼテキスト処理と各種APIを呼んでいるだけという感じです。)、規模的にもかなり小さくできあがりました。

処理はシンプルで、基本的には、MailgunからのHTTP POSTを受けて、TypetalkのAPIを順次、添付ファイルを準備したり、テキストを整形して、呼び出していくだけです。あとは、gcloud deployとやっておけば、MailgunとGAEがメール受信ごとによしなにがんばってくれます。運用がないって最高ですね!

実際の動作例

添付ファイルはTypetalk上でも投稿に紐付いたファイルとして扱われます。画像ならTypetalkがプレビューもしてくれます。

 

少々、分かりづらいですが、メールでの返信は、チャット上でも返信として扱われます。(アイコン同士が線でつながっているところです。)

この機能はメールヘッダのIn-Reply-ToとReferencesをつかって、Message-IDと紐付けていくのですが、これを最初は、別途DBなどをもちたくなかったので、Typetalkのまとめ機能をつかって擬似的に実現していました。(まとめ機能の詳細は、こちらのブログポストなどを参照。)

処理としては、メールのMessage-IDをキーにしたまとめをつくって、メール受信ごとにTypetalk APIをつかってまとめ一覧を取得、In-Reply-ToもしくはReferencesに該当するまとめの名前があればつなぐ、というような形でつくっていました。しかし、このアプローチだと、膨大なまとめが生成され、手動で作られたまとめをさがすのを邪魔すること、Typetalk APIのまとめ取得は一覧すべてを取得しかないので、いつかAPIを呼んで返ってきたJSONがGAEのF1/メモリ128MBでは扱えなくなる日が来そうなことから、これは結局、Cloud DatastoreにMessage-ID, Message-URL(Mailgun上のメールを取得するためのURL), TypetalkのPost IDをセットにして保存することにしました。

これではデータが延々とたまっていくのがちと気になりますが、無料範囲は1GBあるので、この程度のデータを保存しても、数年は無料範囲と予想できるということで、一旦放置することにしました。メールの削除を嫌ってAWSをやめた割に、データが残る作りにしてしまっているので、そのうち、3ヶ月以上前のものを消すような処理を入れてもいいかなとは思っていますが……ただ、数ヶ月前のメールに返信というケースも無きにしも非ずなので微妙に悩み所です。

また、この返信にあたるメールは、SMTPで送受信されたものには、実際には下記のように引用を含みますが、チャットには書き込まれていません。これはMailgunが生成してくれるstripped_textを積極的に使って実現しています。こういうところをよしなに処理してくれるのはいいですね。

Typetalkのメッセージは最大4000文字のようなので、それを越えると、全文を見るリンクを追加されたりもします。世の中のメールはおもったより長いものが多くて、意外とこの機能が発動します。

 

その他、いろんなメールを受信するようにすると、あきらかにそれはRFC的にどうなんだ?というFromを含むメールなどいろいろなものがやってきます。Mailgunはその辺をある程度柔軟に対応していて、RoutesではHTTPリクエストがちゃんと呼ばれるようになっているあたり、本当に偉いなと思います。

こんな形で、メールを送れば自動的にTypetalkにもそれなりに整形されて投稿されるところまでが完成しました。これ以外にも、FromとToのメールアドレスごとにまとめに入れて、同じ人からのメールを一覧できるような機能なども作ってみたのですが、手動でいろいろまとめをつくっているのに比べると、かなりの数のまとめを生成してしまうので、上記同様、通常の利用の邪魔になるだろうということで今はその機能は無効にしています。

実際の運用では、typetalkのトピックID丸出しのメールアドレスをCcに入れるのは憚られると思います。そのため、弊社ではG suiteを採用していて、プロジェクト共有のGroupsがすでに運用されていたりするので、そこのメンバーにtypetalk-[topic id]@example.comという形式のアドレスを足して、そこに対してメールを送ったり、メールの返信の時にCcに入れてやりとりをしたりして、このツールを使用していく形で試しているところです。

最後に

というわけで、ほんのちょっと業務を便利にするツールを作ってみたお話でした。実際、メールを開かずとも、日常的に使っているTypetalkで気軽にメールを確認できるのは良いと言ってくれたメンバーもいるので個人的には作ってみてよかったなとは思ってます。若干、車輪の再発明感はありますが、車輪の再発明+αでちょっとした改善を入れていくのは個人的には結構好みで、作っていると、参考にした既存のものを作った人はどうしてそうしたのかを妄想しながら理解を深めていけるので楽しいなと思ったりはします。車輪の再発明を避けるべきときももちろん多くありますが。

今回作った機能に加えて、このメッセージに返信すれば今度はメールの返信もできる、という機能も作ろうかなとおもったのですが、チャット上のユーザーとメールアドレスを紐付け、且つ、Fromとしてそのアドレスを使えるようにしないといけないという手間を考えると、ちょっとできることに対して大がかりすぎるかなぁということで、今はあくまで、メールを見ない/見れない人にも気軽に情報共有ができるツールという位置づけに留めています。

これ以外にも、Typetalkにアップロードできるファイルサイズはプランに依存しますが、すごく大容量でもない(今は我々はスタンダードプランの50ユーザーなので、50GBまで)ので、今後のことを考えて、Typetalkのwebhookをつかって、メッセージのログとアップロードされたファイルを社内のNASにも自動で保存する、というものを、ファイルを削除せざるを得なくなる前に、作っておこうかなと思ったりしています。と書きながら、Typetalkのファイルを削除するAPIが今は用意されていないことに気づきましたが、容量が逼迫すると、それっぽいメッセージを自力でぽちぽち削除していくことになるんでしょうか。それはそれで大変そうだなぁという気持ちが書きながら沸いてきました。

それはさておき、こういったちょっと業務が楽になる、便利になるツールというのを作ってみたというお話でした。まったく同じ状況の会社はおそらく存在しないので、そのまま流用はできないでしょうが、同様の発想で、今回のツールを改造したり、ちょこっと作ってみたりして、使いやすくして導入することで、だれかがちょっと楽になれるとしたらそれは素敵なことだなと思っています。