はじめに
Hubbleでバックエンドエンジニアをしている @alstrocrack と申します。
Hubble Advent Calendar 2024の15日目1の記事です。
株式会社Hubbleは、端的に言うと契約書管理クラウド HubbleというSaaSを開発しています。このHubbleはお客様に電子メールを送る機会も多々あるのですが、このメールが時たま届かないという事象に遭遇します。これには色んな原因があるのですが、原因の一つにメールバウンス(Ref: バウンスメール)があります。
HubbleはAWSを利用しており、メール送信にはAmazon SESを用いています。そして、Amazon SESでは上述のメールバウンスするとサプレッションリストに登録され、該当のメールアドレスには以降は(そのままでは)メールが送信されないという事象が起こりえます2。
なので、CS(Customer Success)チームから 「このメールアドレスのお客様からメールが届かないというお問い合わせがありました。メールバウンスしているのではないかと思っているのでAmazon SESのサプレッションリストを確認していただいても良いでしょうか?」 という依頼がDevチームに時々やってきます。
実はこれは非常に手間がかかるやりたくない依頼なんですよね。。
なぜ非常に手間がかかる依頼なのか?
(最近AWSのUIが変わって、メールアドレスの削除が以前より簡単になった気がします笑)
Amazon SESのサプレッションリストには(弊社では)1000個近いメールアドレスが存在しています。その中から 検索なし で1つのメールアドレスを探さなければいけません(いけなかった)。これは非常に手間がかかる作業です。
それによくよく考えてみると、そもそも
- CSからDevへの作業依頼
- Devの調査や操作
- DevからCSへの完了報告
と部門を跨いだコミュニケーションも多かれ少なかれ発生していることがわかります。
ここで私が考えたことは、
「そもそもCSチーム自身でサプレッションリストを探したり、該当のメールアドレスを削除できればお互いに楽なのでは?」
CSチームは自分の範囲内でサプレッションリストを検索したり操作したりできるし、Devチームは手間のかかる作業から解放されるはずです。そこでAmazon SESのAPIドキュメント(Ruby)をガサガサしてみると良さげなメソッドがいくつかあります。そこで、Slackの/コマンド
からこのAPIを実行できるようにしようと思いました。
実装の内容
Slackの/コマンド
を用いて、AWS Lambdaの関数のエンドポイントを叩かせて、その関数内でのAPIを実行させるようにしました。
以下は、一部処理を省略したRubyでのAWS Lambda関数の実装です。
require "json"
require "net/http"
require "base64"
require "bundler/inline"
require "cgi"
gemfile do
source "https://rubygems.org"
gem "rexml"
gem "aws-sdk-sesv2"
end
SLACK_ENDPOINT = ENV.fetch("SLACK_ENDPOINT", nil)
SLACK_TOKEN = ENV.fetch("SLACK_TOKEN", nil)
SLACK_CHANNEL = ENV.fetch("SLACK_CHANNEL", nil)
module Event
SEARCH = "search"
DELETE = "delete"
end
module EmailStatus
BOUNCE = "BOUNCE"
COMPLAINT = "COMPLAINT"
end
module Error
NOT_FOUND = "Not Found the email address"
OTHER = "Something went wrong"
end
module Message
module Search
SUCCESS = "🚀 The email address has been discovered!"
FAILURE = "😔 We could not find the email address..."
end
module Delete
SUCCESS = "🚀 We have successfully removed the email address from the bounce list!"
FAILURE = "😔 We Could not remove the email address from the bounce list..."
end
end
def lambda_handler(event:, context:)
email = event["email"] # イベントデータからメールアドレスを取得
case type
when Event::SEARCH
res = search(email)
result_message = res[:is_success] ? Message::Search::SUCCESS : Message::Search::FAILURE
send_slack(result_message, email, res[:reason])
when Event::DELETE
res = delete(email)
result_message = res[:is_success] ? Message::Delete::SUCCESS : Message::Delete::FAILURE
send_slack(result_message, email, res[:reason])
else
send_slack("That type doesn't exist...", email)
end
rescue StandardError => e
puts(e)
end
def search(email)
result = { is_success: false, reason: "" }
res = client.get_suppressed_destination({ email_address: email })
case res.suppressed_destination.reason
when EmailStatus::BOUNCE
result[:is_success] = true
result[:reason] = EmailStatus::BOUNCE
when EmailStatus::COMPLAINT
result[:is_success] = true
result[:reason] = EmailStatus::COMPLAINT
end
result
rescue Aws::SESV2::Errors::NotFoundException => e
{ is_success: false, reason: Error::NOT_FOUND }
rescue StandardError => e
{ is_success: false, reason: Error::OTHER }
end
def delete(email)
client.delete_suppressed_destination({ email_address: email })
{ is_success: true }
rescue Aws::SESV2::Errors::NotFoundException => e
{ is_success: false, reason: Error::NOT_FOUND }
rescue StandardError => e
{ is_success: false, reason: Error::OTHER }
end
def client
Aws::SESV2::Client.new(region: ENV.fetch("REGION", nil),
access_key_id: ENV.fetch("ACCESS_KEY_ID", nil),
secret_access_key: ENV.fetch("SECRET_ACCESS_KEY", nil))
end
def send_slack(result_message, email, reason = nil)
# Slack SDKを使用して任意のメッセージを送信する処理を記述
# 主題から逸れるため省略
end
ちなみにRubyでの小ネタですが、RubyでのGem管理にGemfileを用いるのは一般的ですが、上記の実装にもあるように以下のような記述でファイルを分けることなくスッキリ記述できます。(Ref: GitHub Gist)
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "rexml"
gem "aws-sdk-sesv2"
end
実際の画面の様子
メールアドレスの検索(Not Found)
メールアドレスの検索(Found)& サプレッションリストからの削除
CSチームに使用していただいている様子
それで結局何が言いたいんですか、ただの自慢ですか??
違います!(まあ、半分そうかもしれません笑)冗談はさておき、我々のようなリソースに限りのあるスタートアップなどは定期的に自社の業務を見直して効率化するべしという話です。Hubbleにjoinしてビックリしたのは各所にSlack ワークフローとやZapierなどを用いてトイルを解消する試みが多くみられたところです。このように業務効率化しないと、いつまで経っても手間のかかる業務と向き合うハメになります。このサプレッションリストを洗うタスクには体感で毎回数十分かかっていた&コミュニケーションのリソースも考慮すると大きな業務効率化だと考えています。
Bizの業務にも関心を持つべし
エンジニアの三大美徳の中には「怠惰」がありますが、一般的な社会人としては勤勉こそが美徳です。雑談している中で気づいたりしますが、けっこう大変そうな作業を手作業で行っていたりします。その上作業内容を聞くと、エンジニアは『それって自動化できそうじゃない?』と思うことがあります。 Bizは『そんなことできるの?』ってなったりしますが。そこにも目をつけて前述のZapierなどでの業務効率化や最終的には今回のようなショートコードを書いて、BizもDevもWin-Winになるような施策を打ったり、提案したりするとお互いにコアの業務にさらに集中することができます。
最後に
ただし、コード書くのは最終手段です。できるだけ出来合いのものを使って工夫しましょう。結構、コード書くのもめんどくさいですし、何なら「勝手にLambda関数増やすなよ、管理すんのがめんどくさいだろ」ってSREエンジニアに言われたりしますから。。。。。
次回は @embokoir さんです!