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
defp deps do
[
{:req, "~> 0.5"}
]
endSend
# 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
endiex> 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.
# 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)