6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HubbleAdvent Calendar 2024

Day 20

CSチームからの「Amazon SESのサプレッションリストを調べていただけませんか?」という問い合わせを0にした話

Last updated at Posted at 2024-12-19

はじめに

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つのメールアドレスを探さなければいけません(いけなかった)。これは非常に手間がかかる作業です。

それによくよく考えてみると、そもそも

  1. CSからDevへの作業依頼
  2. Devの調査や操作
  3. DevからCSへの完了報告

と部門を跨いだコミュニケーションも多かれ少なかれ発生していることがわかります。

ここで私が考えたことは、
「そもそもCSチーム自身でサプレッションリストを探したり、該当のメールアドレスを削除できればお互いに楽なのでは?」

CSチームは自分の範囲内でサプレッションリストを検索したり操作したりできるし、Devチームは手間のかかる作業から解放されるはずです。そこでAmazon SESのAPIドキュメント(Ruby)をガサガサしてみると良さげなメソッドがいくつかあります。そこで、Slackの/コマンドからこのAPIを実行できるようにしようと思いました。

実装の内容

Slackの/コマンドを用いて、AWS Lambdaの関数のエンドポイントを叩かせて、その関数内でのAPIを実行させるようにしました。
以下は、一部処理を省略したRubyでのAWS Lambda関数の実装です。

lambda_function.rb
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)

(サンプル用に、実際のメールアドレスを表示しています)
スクリーンショット 2024-12-19 16.13.58.png

メールアドレスの検索(Found)& サプレッションリストからの削除

bounce_list.png

CSチームに使用していただいている様子

cs.png

それで結局何が言いたいんですか、ただの自慢ですか??

違います!(まあ、半分そうかもしれません笑)冗談はさておき、我々のようなリソースに限りのあるスタートアップなどは定期的に自社の業務を見直して効率化するべしという話です。Hubbleにjoinしてビックリしたのは各所にSlack ワークフローとやZapierなどを用いてトイルを解消する試みが多くみられたところです。このように業務効率化しないと、いつまで経っても手間のかかる業務と向き合うハメになります。このサプレッションリストを洗うタスクには体感で毎回数十分かかっていた&コミュニケーションのリソースも考慮すると大きな業務効率化だと考えています。

Bizの業務にも関心を持つべし

エンジニアの三大美徳の中には「怠惰」がありますが、一般的な社会人としては勤勉こそが美徳です。雑談している中で気づいたりしますが、けっこう大変そうな作業を手作業で行っていたりします。その上作業内容を聞くと、エンジニアは『それって自動化できそうじゃない?』と思うことがあります。 Bizは『そんなことできるの?』ってなったりしますが。そこにも目をつけて前述のZapierなどでの業務効率化や最終的には今回のようなショートコードを書いて、BizもDevもWin-Winになるような施策を打ったり、提案したりするとお互いにコアの業務にさらに集中することができます。

最後に

ただし、コード書くのは最終手段です。できるだけ出来合いのものを使って工夫しましょう。結構、コード書くのもめんどくさいですし、何なら「勝手にLambda関数増やすなよ、管理すんのがめんどくさいだろ」ってSREエンジニアに言われたりしますから。。。。。

次回は @embokoir さんです!

  1. 平日のみの投稿なので20日ですが、15日目の記事としています。

  2. これは厳密な説明ではないため、詳しくは別のソースも参照ください。

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?