Phoenix

A Swoosh adapter, so every Phoenix mailer and email in the app goes through emails.sh.

Phoenix generates a Swoosh mailer for every new application. Point it at an adapter of yours and the emails your context modules already build go out through the API, with no other change.

Configure

config/runtime.exs
# config/runtime.exs
# Read at boot, not compiled in. The key comes from
# https://emails.sh/dashboard/api-keys.
config :acme, Acme.Mailer,
  adapter: Acme.EmailsshAdapter,
  api_key: System.fetch_env!("EMAILSSH_API_KEY")

The adapter

lib/acme/emailssh_adapter.ex
# lib/acme/emailssh_adapter.ex
defmodule Acme.EmailsshAdapter do
  @moduledoc "Swoosh adapter for emails.sh."
  use Swoosh.Adapter, required_config: [:api_key]

  @endpoint "https://emails.sh/v1/emails"

  @impl true
  def deliver(%Swoosh.Email{} = email, config) do
    body =
      %{
        from: address(email.from),
        to: Enum.map(email.to, &elem(&1, 1)),
        subject: email.subject,
        html: email.html_body,
        text: email.text_body
      }
      |> maybe_put(:cc, Enum.map(email.cc || [], &elem(&1, 1)))
      |> maybe_put(:bcc, Enum.map(email.bcc || [], &elem(&1, 1)))
      |> Map.reject(fn {_k, v} -> is_nil(v) end)

    case Req.post(@endpoint, json: body, auth: {:bearer, config[:api_key]}, receive_timeout: 10_000) do
      {:ok, %{status: status, body: %{"id" => id}}} when status < 400 ->
        {:ok, %{id: id}}

      {:ok, %{body: %{"error" => %{"code" => code, "message" => message}}}} ->
        {:error, {code, message}}

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp address({nil, addr}), do: addr
  defp address({name, addr}), do: "#{name} <#{addr}>"

  defp maybe_put(map, _key, []), do: map
  defp maybe_put(map, key, value), do: Map.put(map, key, value)
end

Use it

lib/acme/accounts/user_notifier.ex
# lib/acme/accounts/user_notifier.ex, as generated by phx.gen.auth
defmodule Acme.Accounts.UserNotifier do
  import Swoosh.Email
  alias Acme.Mailer

  def deliver_confirmation_instructions(user, url) do
    new()
    |> to({user.name, user.email})
    |> from({"Acme", "hello@acme.com"})
    |> subject("Confirm your Acme account")
    |> html_body("<p>Confirm your account: <a href=\"#{url}\">#{url}</a></p>")
    |> text_body("Confirm your account: #{url}")
    |> Mailer.deliver()
  end
end