Elixir

Req in a small module, supervised the way a Phoenix app expects.

There is no emails.sh hex package. Req is the HTTP client most Elixir projects reach for, and the module below is the whole integration.

Dependency

mix.exs, then mix deps.get
# mix.exs
defp deps do
  [
    {:req, "~> 0.5"}
  ]
end

Send

lib/acme/email.ex
# lib/acme/email.ex
defmodule Acme.Email do
  @moduledoc """
  Transactional email over emails.sh.

  The key comes from https://emails.sh/dashboard/api-keys and is read from the
  EMAILSSH_API_KEY environment variable at runtime, never compiled in.
  """

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

  @spec send(keyword()) :: {:ok, String.t()} | {:error, String.t()}
  def send(opts) do
    body = %{
      from: Keyword.get(opts, :from, "Acme <hello@acme.com>"),
      to: [Keyword.fetch!(opts, :to)],
      subject: Keyword.fetch!(opts, :subject),
      html: Keyword.fetch!(opts, :html)
    }

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

      {:ok, %{body: %{"error" => %{"code" => code, "message" => message} = refusal}}} ->
        # next says what to do about it, so keep it rather than the code alone.
        {:error, "#{code}: #{message} #{refusal["next"]}"}

      {:error, reason} ->
        {:error, "emails.sh unreachable: #{inspect(reason)}"}
    end
  end

  defp key do
    System.fetch_env!("EMAILSSH_API_KEY")
  end
end
In iex
iex> Acme.Email.send(to: "ada@example.com", subject: "Your receipt from Acme", html: "<p>Thanks.</p>")
{:ok, "em_01J9X8Q2K7Y4RN3M"}

Off the request path

A send takes tens of milliseconds, but it is still a network call in front of a user. Task.Supervisor keeps it off the response, and a crash in the task does not take the caller with it.

Supervised, fire and forget
# In your application supervision tree
children = [
  {Task.Supervisor, name: Acme.TaskSupervisor}
]

# At the call site
Task.Supervisor.start_child(Acme.TaskSupervisor, fn ->
  Acme.Email.send(
    to: user.email,
    subject: "Welcome to Acme",
    html: "<p>Confirm your address to finish signing up.</p>"
  )
end)