Google Workspace無償版の独自ドメインメールをSendGrid + PipeDreamに移行する


サマリ

やけに記事が長くなってしまったので簡単に。

  1. SengGrid InboundParseで受けて、
  2. PipeDream WorkflowでSendGrid APIを使って、
  3. 自身のGmailアドレスに転送する

以上! です。

経緯

10年以上も無償で使わせてくれたGoogle先生には感謝しか無いのですが、流石にというか、ついにというか2022/5/1をもって終了のお知らせが来ました。

https://support.google.com/a/answer/2855120?hl=ja

当初まだG SuiteでもないGoogle Appsという名前だった時代にEarly版を申し込んで仲間内でグループウェアのように運用してみたり、家族間で使ってみたり色々試しました。おかげでOffice365(現Microsoft365)の考え方もスッと入ってきたと言うか、組織内コレボレーションツールとしてはGoogle Workspaceのほうが2,3歩先に行ってる気がします。

さて、そんな楽しいときもいつかは終わりが来るもの。桜舞い散るシーズンですからね。

課題

自分の使い方としては自組織があるわけでもなく、単純におもちゃとして使っていた側面が強いので正直そんなには困っていません。したがって有償版に移行するモチベーションはあまりないのです。(10年以上も試用させてもらって申し訳ないですが)

移行を検討すべきポイントは二点だけでした。

  1. Gmail
  2. Google Drive

Gmail

2つのGmailを使い分けていました。

  1. 基本的に本名で銀行とかネットショップとかに使うgmail.comメールアドレス
  2. NewGyu名でSNSや開発ツールに使う独自ドメインメールアドレス(Google Workspaceのメールボックス)

メールの受信

色々なWebサービスにNewGyu名義アドレスで登録していて、それらの通知メールがNewGyuメールボックスに届きます。5/1からそれらの通知などが届かないと困るので、これは一番のペインポイントです。

メールの送信

NewGyu名義をFromとしてメール送信することに特にこだわりはありません。
こちらは優先度は低いのですが、Gmailが複数のFromを使い分ける機能を有しているので既に解決手段は見えています。

Google Drive

NewGyu名義の方のGoogle Driveに音楽とか動画とかの比較的大きなファイルを保管していました。本名Google Driveの方も写真なんかで結構埋まってるのでこの移行場所に少し悩みましたが、こちらはもう金で解決…Google Oneで100GBに増量しました。250円/月ですしおすし。

久しぶりのGCPの勉強も兼ねてGoogle Cloud StorageにおいてExoPlayerでストリーミング再生するアプリをJetpack Composeで書いてみようという気持ちが湧き上がりましたが…引っ込みました。

で、本題 Google WorkspaceのGmailを引っ越す

ドメインネームサーバーはどこ?

メールの送受信をいじるにはMXレコードの設定が必要になるのでDNSがどうなっているかを確認しておく必要がありました。

ほとんど忘れかけていたのですが、利用開始した当時、独自ドメインの取得はGoogle Apps登録の過程で暗黙に行われていたのですが、どうやらGoDaddyで登録されているようです。

下図はGoogle Adminのドメイン管理>詳細を確認した結果です。

ここで「DNSコンソールにログイン」すると https://dcc.secureserver.net/domains というところに遷移します。

はて、この dcc.secureserver.net というドメインはGoDaddyなのか? godaddy.comではないし、GoDaddyにサインインしようとすると「お前はユーザーではない」と言われてしまいます。上部にGoogle Worskspaceのバナーが付いているのでGoogle Workspaceの機能の一部でしょうか・・・Google Workspace 無償版が終わったら使えなくなるとか無いよね?? ということでサポート問い合わせしてみました。

お客様は G Suite のライセンスを Google Domains を使用して "GoDaddy" 内でのドメインを契約されたことを確認しました。

Google Workspace のライセンスとドメインのライセンスは異なるもとなっておりますので現在使用されているライセンスを契約されてもドメインは続けて管理することは可能でございますのでご安心ください。

なるほど、Google DomainsというサービスでGoDaddyをラップしているのですね。Google Domainsの管理画面にもドメインが出てくることが確認できました。

https://dcc.secureserver.net/domains は管理画面が質素でやりづらいので移管は検討したいのですが、一旦ドメイン管理については手当せずとも継続できるものとわかりました。

新しいメール受信サーバーはどうしようか

自分でメールサーバーを立てる気は毛頭ありません。PostfixやSemdmailをいじるのなんてもう20年来やってませんし、仮想マシン管理もしたくありません。

SendGrid Inbound Parseで受ける

調べていくとメール送信で有名なSendGridに受信メールを処理する機能があるようです。知らなかった。

https://sendgrid.kke.co.jp/blog/?p=9806

Inbound Parse という機能は以下のようなものです。

  • MTAとしてMXレコード設定されたドメインのメールを受けることができる
  • メールボックスは持っていないので保存は出来ない
  • 受けたタイミングで外部のWebHookにPOSTリクエストを送ることができる
    • POSTのペイロードにJSONとして構造化されたメールの内容を入れてくれる

PipedreamでHook

さて、SendGrid Inbound Parseで受けたメールをどこかのWebHookにPOSTで送りつける必要があります。

ここはいくつか候補がありましたが、料金表や機能表とにらめっこした末に選んだのはPipedreamでした。基本的にノーコードなのですけど、いざとなったら Node.js/Python/Go/Bash でコードを書けるのが心強いです。

  • Google Cloud Functions
    • AWS LambdaでもAzure Funcitonsでも良いのですけど、GCPを久しぶりに触りたいというかすかなモチベーションもあったので…
  • Zapier
    • SendGridの記事で紹介されていたのはこちらになります
  • IFTTT
    • これ系の牽引者ですね、昔使っていた記憶があって思い出しました
  • Pipedream
    • SendGridの記事の片隅に紹介されていました

久しぶりのGCPを触りたいというモチベーションはどこへ・・・楽を覚えた人間はもう昔には戻れないのです。

Hookで受けたメールをSendGrid Send APIで自分のメールボックスに転送

今度はHookで受けたJSON化されたメールの内容をどうするかです。

これも候補がありましたが自分の個人Gmailに転送されればいいじゃんというソリューションにしました。SendGridはメールを送ることが大得意なソリューションなのでSendGrid Send APIでメール送信をします。

  • Google Cloud Storageなどに保存
    • 単価は超安い
    • 読むために別途Viewerを作る必要がある
  • 個人Gmailに転送
    • 実装が手軽
    • Gmailにはラベル付けと優れた検索機能があるので混ざっても支障はない

楽を覚えた人間は・・・

結果的にできあがったのはSendGridとPipedreamによる連携技でした。

技術的詳細

さほど難しいことはやっていないのですがいくつか注意すべき点があるので残しておきます。

SendGrid Sender Authentication

SendGridは基本的にメルマガを送信したりするメール送信のソリューションです。メール送信する場合に問題になるのが悪質なスパムメールで、メールの仕様上Fromに表示される送り主は簡単に偽装出来てしまいます。

SendGridとしてこの様にFromをなりすました迷惑メールをばら撒かれては困るわけで、本当にFromに指定したメールアドレス、ドメインを使う適格者なのかを示す必要があります。

"Sender"と言う名称ですがInbound Parseで受けるためにも必要です。

https://sendgrid.kke.co.jp/docs/Tutorials/D_Improve_Deliverability/using_whitelabel.html

手順はこちらのドキュメントどおりで、大まかな流れとしては以下です。

  1. 送受信に使用するドメイン名をSendGridに教える
  2. SendGridから指定されたCNAMEレコードを自身の管理するネームサーバーに登録する
  3. DNSレコードが反映されたらSendGridからVerifyする

SendGrid Inbound Parse

非常に簡単にですが、

  1. 自身の管理するネームサーバーで mx.sendgrid.net を指すMXレコードを登録
  2. 上記のMXレコードを指定した独自ドメインをSendGridに設定する
  3. 受信したときに実行されるWebHookのURL(本件ではpipedreamのTriggerのURL)をSendGridに設定する

というだけです。

Pipedream Workflow

Workflowの概念

https://pipedream.com/docs/workflows/

Workflowは

  • Trigger(Source)
  • Action

の2つの概念から成り立ちます。

Trigger(Source)

TriggerとSourceという概念があって同じようなニュアンスで使われているので少々混乱しましたがWhat's the difference between an event source and a trigger?という記述を参考にすると、

  • SourceはPipedreamの外界からイベントを受けるインターフェース
  • Sourceが受けて内部向けにイベントを構造化してemitする
  • Triggerは特定一つのSourceを参照するもの、特定Workflowの起点となるもの
  • Sourceは複数のWorkspaceのTriggerから参照されることができる

というもののようです。

Sourceは柔軟に設計されていて、複数のWorkflowを使いこなす際には便利でしょうが、本件の目的ではTriggerとほぼ同義と考えて良さそうです。

Action

https://pipedream.com/docs/components/actions/

Triggerが入力で、Actionはその入力を受けてProcessing/Outputをするものになります。その多くは外部サービスのAPIをCallするものになっています。
そして、Pipedreamには事前定義のActionが数多く用意されています。

Actionの多くはNode.jsで実装されているようで、Axiosを使って対象のAPIをCallしているコード実装を確認することが出来ます。

async (params, auths) => {
	const axios = require('axios')

	return await require("@pipedreamhq/platform").axios(this, {
	  url: `https://api.sendgrid.com/v3/mail/send`,
	  headers: {
	    Authorization: `Bearer ${auths.sendgrid.api_key}`,
	  },
	  method: 'POST',
	  data: {
	    "personalizations": [{
	      "to": [{
		"email": params.to_email,
		"name": params.to_name
	      }],
	      "subject": params.subject,
	      "headers": params.headers,
	      "substitutions": params.substitutions,
	      "custom_args": params.custom_args,
	      "send_at": params.send_at
	    }],
	    "from": {
	      "email": params.from_email,
	      "name": params.from_name
	    },
	    "reply_to": {
	      "email": params.reply_to_email,
	      "name": params.reply_to_name
	    },
	    "content": [{
	      "type": params.type,
	      "value": params.value
	    }],
	    "template_id": params.template_id,
	    "sections": params.sections,
	    "categories": params.categories,
	    "batch_id": params.batch_id,
	    "ip_pool_name": params.ip_pool_name
	  }
	})
}

※と、思うのですがイマイチAppsとActionsの違いが理解できていません。

Connected Accounts

https://pipedream.com/docs/connected-accounts/

Actionで外部のAPIを利用するには多くの場合AccessTokenが必要になります。そして昨今では多くの場合それは

  • API Key形式
  • OAuth2.0に沿ったAccessToken形式

のいずれかでSendGridに関しては前者になっています。

SendGrid API Key

https://sendgrid.kke.co.jp/docs/User_Manual_JP/Settings/api_keys.html

SendGridはAPI Keyの権限制御がかなり細かくできるようになっています。
本件ではメール送信だけできれば良いので、

  • Restricted Access
  • Mail Send のFullAccessのみ

としています。

心残り、妥協

HTMLメール、添付ファイル

SendGridのInbound ParseはHTMLメールも添付ファイルもJSON構造に含まれてくるので対応は可能だと思いますが、割愛しました。

メールロストの可能性

PipedreamではSendGridからWebHookのPOSTリクエストを受けたあとにemitして後続のActionに処理を移しますがこの処理は非同期に行われ、Actionの終了をまたずにSourceが応答します。したがって、何らかの不備でActionが失敗してもSendGrid側からは成功として扱われてWebHookのリトライをしません。

SendGridとしてはリトライ機構を持っていますが、PipedreamのSource実装の都合です。

本物のMTAであればRelyが失敗したことが送信元のMTAに伝わるので再送される可能性が高いですが、この仕組では送信元のMTAも無事に送った気持ちになっていると思われます。

Pipedreamのemitとrespondのタイミングをどこまでいじれるかわかりませんが、ひょっとしたら対応できるのかもしれません。

ドメインに対して無作為なメールボックスへのメール送信攻撃

SendGridのInbound Parseでは xxxx@mydomain.net 宛にメール受信した場合、xxxxの部分が何であれWebhookを実行します。そのため実在しないメールアドレスに対しての送信であってもPipedreamのSourceはTriggerされてしまいます。

こちらについてはPipedream側で送信先アドレスの事前検証を行うことで対処可能だと思われますが、まだやってません。

Fromアドレスが本当の送信者ではなくなる

Sender Authenticationのところで述べた通り、SendGridのSend APIではFrom欄に指定できるアドレスに制限があります。[email protected]から送られてきたメールをそのまま[email protected]からのメールとしてSend APIで送信することが出来ません。

ここは本当はなんとかしたいところでしたが、Sender Authenticationの概念を考えると他のメール送信サービスを使っても難しいだろうなと考えました。

まとめ

以上、思ったより長くなってしまいましたが、

  • SendGrid Inbound Parseでメールを受けることが出来ます
  • PipedreamでSendGrind Send APIを実行してメールを別のところに送ることができます

という手段を使ってGoogle Workspaceで利用していた独自ドメインでのメール受信の代替としました。