Skip to content

Commit

Permalink
Migrate feedback mailer from Mailgun to Amazon SES
Browse files Browse the repository at this point in the history
We are about to start getting charged for Mailgun, whereas at the
volumes we use, SES should be free.
  • Loading branch information
phildarnowsky committed Feb 28, 2020
1 parent b74ad73 commit acc930d
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 89 deletions.
3 changes: 2 additions & 1 deletion apps/feedback/config/test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use Mix.Config

config :feedback,
time_fetcher: Feedback.FakeDateTime
time_fetcher: Feedback.FakeDateTime,
exaws_perform_fn: &Feedback.Test.mock_perform/2
47 changes: 24 additions & 23 deletions apps/feedback/lib/mailer.ex
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
defmodule Feedback.Mailer do
@moduledoc false

use Mailgun.Client
require Logger

# Mix isn't available at runtime
@mix_env Mix.env()

def config do
[
domain: Application.get_env(:feedback, :mailgun_domain),
key: Application.get_env(:feedback, :mailgun_key),
test_file_path: Application.get_env(:feedback, :test_mail_file),
mode: @mix_env
]
end

@spec send_heat_ticket(Feedback.Message.t(), [map()]) :: {:ok, any} | {:error, any}
def send_heat_ticket(message, photo_info) do
request_response = if message.request_response, do: "Yes", else: "No"
Expand Down Expand Up @@ -48,21 +38,32 @@ defmodule Feedback.Mailer do
</INCIDENT>
"""

opts = [
to: Application.get_env(:feedback, :support_ticket_to_email),
from: Application.get_env(:feedback, :support_ticket_from_email),
subject: "MBTA Customer Comment Form",
text: body
]

opts =
message =
if photo_info do
[{:attachments, photo_info} | opts]
photo_info
|> Enum.reduce(
Mail.build_multipart(),
fn attachment, message -> Mail.put_attachment(message, attachment) end
)
else
opts
Mail.build()
end

Mailgun.Client.send_email(config(), opts)
message =
message
|> Mail.put_to(Application.get_env(:feedback, :support_ticket_to_email))
|> Mail.put_from(Application.get_env(:feedback, :support_ticket_from_email))
|> Mail.put_subject("MBTA Customer Comment Form")
|> Mail.put_text(body)

exaws_perform_fn =
Application.get_env(:feedback, :exaws_perform_fn, &ExAws.Operation.perform/2)

{:ok, _} =
message
|> Mail.Renderers.RFC2822.render()
|> ExAws.SES.send_raw_email()
|> exaws_perform_fn.(ExAws.Config.new(:ses))
end

defp format_name(nil) do
Expand Down
4 changes: 3 additions & 1 deletion apps/feedback/lib/repo.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule Feedback.Repo do
@moduledoc false

@spec send_ticket(Feedback.Message.t()) :: {:ok, any} | {:error, any}
def send_ticket(message) do
Feedback.Mailer.send_heat_ticket(message, photo_attachment(message.photos))
Expand All @@ -7,7 +9,7 @@ defmodule Feedback.Repo do
@spec photo_attachment([Plug.Upload.t()]) :: [%{path: String.t(), filename: String.t()}] | nil
def photo_attachment([%Plug.Upload{} | _rest] = photos) do
Enum.map(photos, fn %Plug.Upload{path: path, filename: filename} ->
%{path: path, filename: filename}
{filename, File.read!(path)}
end)
end

Expand Down
33 changes: 33 additions & 0 deletions apps/feedback/lib/test.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule Feedback.Test do
@moduledoc false

def latest_message do
file = Application.get_env(:feedback, :test_mail_file)
body = File.read!(file)
Expand All @@ -11,4 +13,35 @@ defmodule Feedback.Test do
raise "Error parsing test mail file: #{inspect(body)}"
end
end

def mock_perform(%{params: %{"RawMessage.Data" => raw_message}}, _config) do
parsed_message = raw_message |> Base.decode64!() |> Mail.Parsers.RFC2822.parse()

attachments =
if parsed_message.multipart do
tl(parsed_message.parts)
else
[]
end

message_json =
%{
to: parsed_message.headers["to"],
text: parsed_message.body,
attachments: attachments |> Enum.map(&simplify_attachment/1)
}
|> Poison.encode!()

file = Application.get_env(:feedback, :test_mail_file)
File.write!(file, message_json)

{:ok, :status_info_that_gets_ignored_by_caller}
end

defp simplify_attachment(%Mail.Message{
body: data,
headers: %{"content-disposition" => ["attachment", {"filename", filename}]}
}) do
%{"filename" => filename, "data" => data}
end
end
7 changes: 5 additions & 2 deletions apps/feedback/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule Feedback.Mixfile do
#
# Type "mix help compile.app" for more information
def application do
[applications: [:logger, :mailgun, :timex, :briefly]]
[applications: [:logger, :mailgun, :timex, :briefly, :ex_aws, :ex_aws_ses, :mail]]
end

# Dependencies can be Hex packages:
Expand All @@ -40,7 +40,10 @@ defmodule Feedback.Mixfile do
{:timex, ">= 2.0.0"},
{:briefly, "~> 0.3"},
{:excoveralls, "~> 0.5", only: :test},
{:plug, "~> 1.7.2"}
{:plug, "~> 1.7.2"},
{:ex_aws, "~> 2.1.2"},
{:ex_aws_ses, "~> 2.1.1"},
{:mail, "~> 0.2"}
]
end
end
26 changes: 18 additions & 8 deletions apps/feedback/test/mailer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule Feedback.MailerTest do
nil
)

assert Test.latest_message()["to"] == "[email protected]"
assert Test.latest_message()["to"] == ["[email protected]"]
end

test "has the body format that heat 2 expects" do
Expand Down Expand Up @@ -54,7 +54,10 @@ defmodule Feedback.MailerTest do
end

test "uses the comments of the message for the description" do
Mailer.send_heat_ticket(%{@base_message | comments: "major issue to report"}, nil)
Mailer.send_heat_ticket(
%{@base_message | comments: "major issue to report"},
nil
)

assert Test.latest_message()["text"] =~
"<DESCRIPTION>major issue to report</DESCRIPTION>"
Expand All @@ -81,7 +84,11 @@ defmodule Feedback.MailerTest do
end

test "the email does not have leading or trailing spaces" do
Mailer.send_heat_ticket(%{@base_message | email: " [email protected] "}, nil)
Mailer.send_heat_ticket(
%{@base_message | email: " [email protected] "},
nil
)

assert Test.latest_message()["text"] =~ "<EMAILID>[email protected]</EMAILID>"
end

Expand Down Expand Up @@ -111,14 +118,17 @@ defmodule Feedback.MailerTest do
end

test "can attach a photo" do
Mailer.send_heat_ticket(@base_message, [
%{path: "/tmp/nonsense.txt", filename: "test.png"}
])
Mailer.send_heat_ticket(
@base_message,
[
{"test.png", "png data goes here"}
]
)

assert Test.latest_message()["attachments"] == [
%{
"path" => "/tmp/nonsense.txt",
"filename" => "test.png"
"filename" => "test.png",
"data" => "png data goes here"
}
]
end
Expand Down
48 changes: 0 additions & 48 deletions apps/feedback/test/repo_test.exs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -191,21 +191,28 @@ defmodule SiteWeb.CustomerSupportControllerTest do
end

test "attaches photos in params", %{conn: conn} do
File.write!("/tmp/upload-1", "upload 1 data")
File.write!("/tmp/upload-2", "upload 2 data")

params =
valid_no_response_data()
|> Map.put("photos", [
%Plug.Upload{filename: "photo-1.jpg", path: "/tmp/upload-1"},
%Plug.Upload{filename: "photo-2.jpg", path: "/tmp/upload-2"}
])

conn = post(conn, customer_support_path(conn, :submit), %{"support" => params})
conn =
post(conn, customer_support_path(conn, :submit), %{
"support" => params
})

wait_for_ticket_task(conn)

attachments = Feedback.Test.latest_message()["attachments"]

assert attachments == [
%{"filename" => "photo-1.jpg", "path" => "/tmp/upload-1"},
%{"filename" => "photo-2.jpg", "path" => "/tmp/upload-2"}
%{"filename" => "photo-1.jpg", "data" => "upload 1 data"},
%{"filename" => "photo-2.jpg", "data" => "upload 2 data"}
]
end

Expand All @@ -217,7 +224,10 @@ defmodule SiteWeb.CustomerSupportControllerTest do
%Plug.Upload{filename: "runme.exe"}
])

conn = post(conn, customer_support_path(conn, :submit), %{"support" => params})
conn =
post(conn, customer_support_path(conn, :submit), %{
"support" => params
})

assert "photos" in conn.assigns.errors
end
Expand All @@ -231,7 +241,9 @@ defmodule SiteWeb.CustomerSupportControllerTest do
Enum.reduce(1..4, conn, fn _, acc ->
acc
|> recycle()
|> post(path, %{"support" => valid_request_response_data()})
|> post(path, %{
"support" => valid_request_response_data()
})
end)

assert conn.status == 429
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule DotCom.Mixfile do
],
test_coverage: [tool: ExCoveralls],
dialyzer: [
plt_add_apps: [:mix, :phoenix_live_reload, :laboratory],
plt_add_apps: [:mix, :phoenix_live_reload, :laboratory, :ex_aws, :ex_aws_ses],
flags: [:race_conditions, :unmatched_returns],
ignore_warnings: ".dialyzer.ignore-warnings"
],
Expand Down
3 changes: 3 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm"},
"ehmon": {:git, "https://github.com/mbta/ehmon.git", "1fb603262bd02d74a16183bd8f344dcace9d7561", []},
"erlex": {:hex, :erlex, "0.1.6", "c01c889363168d3fdd23f4211647d8a34c0f9a21ec726762312e08e083f3d47e", [:mix], [], "hexpm"},
"ex_aws": {:hex, :ex_aws, "2.1.2", "a0f8443556eb527e042c749eb87347d9cd1d13688da4e5354ddee51f99272360", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
"ex_aws_ses": {:hex, :ex_aws_ses, "2.1.1", "7324f2d0038203c70f8b9f5d0f0473a1f473b94e8eca57c84bf4b6aac04d584b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"exactor": {:hex, :exactor, "2.2.3", "a6972f43bb6160afeb73e1d8ab45ba604cd0ac8b5244c557093f6e92ce582786", [:mix], []},
"excoveralls": {:hex, :excoveralls, "0.7.1", "3dd659db19c290692b5e2c4a2365ae6d4488091a1ba58f62dcbdaa0c03da5491", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]},
Expand All @@ -37,6 +39,7 @@
"jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], []},
"laboratory": {:git, "https://github.com/paulswartz/laboratory.git", "24adbe2cb18d8368e140ab5e13c8f5b75adea742", [ref: "cookie_opts"]},
"logster": {:hex, :logster, "0.4.3", "a20e9c60e94847e60064d99042d10b1d03abac354c2b360842bf9b3386e56b4a", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},
"mail": {:hex, :mail, "0.2.1", "65ba7cf79c07b79a3ce5946397a06b87d8b5f7e5f7b6d31ee8bcdfe940e27b86", [:mix], [], "hexpm"},
"mailgun": {:hex, :mailgun, "0.1.2", "37c1306675cf27a66a13dea3c9d479da2a990f0aed296b5addbd0b07529b667d", [:mix], [{:poison, "~> 1.4", [hex: :poison, optional: false]}]},
"makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
Expand Down

0 comments on commit acc930d

Please sign in to comment.