# Send email from Phoenix

Send transactional email from Phoenix with emails.sh. A Swoosh adapter so your existing emails and Swoosh.Email structs keep working.

A Phoenix app generated in the last few years already has Swoosh and a MyApp.Mailer module. The clean integration is a Swoosh adapter, so every existing Swoosh.Email goes out over the emails.sh HTTP API without touching the emails themselves.

## Setup

1. **Create a key** https://emails.sh/dashboard issues a key starting with esh_.
1. **Read it in runtime.exs** System.fetch_env!("EMAILSSH_API_KEY") in config/runtime.exs. config.exs is evaluated at compile time, so a key read there gets baked into the release image.
1. **Add the adapter** lib/my_app/emailssh_adapter.ex below implements the Swoosh.Adapter behaviour, which is one deliver/2 callback.
1. **Point your Mailer at it** config :my_app, MyApp.Mailer, adapter: MyApp.EmailsshAdapter. Your existing MyApp.Mailer.deliver/1 calls need no change.

## Install

```bash
{:swoosh, "~> 1.16"}, {:req, "~> 0.5"}
```

## config/runtime.exs

```
import Config

if config_env() == :prod do
  config :my_app, MyApp.Mailer,
    adapter: MyApp.EmailsshAdapter,
    api_key: System.fetch_env!("EMAILSSH_API_KEY")
end

# In dev, keep Swoosh's local adapter so mail lands in the preview at
# http://localhost:4000/dev/mailbox instead of a real inbox.
if config_env() == :dev do
  config :my_app, MyApp.Mailer, adapter: Swoosh.Adapters.Local
end
```

## The Swoosh adapter

lib/my_app/emailssh_adapter.ex (Swoosh 1.16)

```
defmodule MyApp.EmailsshAdapter do
  @moduledoc """
  Swoosh adapter for the emails.sh HTTP API.

  Implementing the behaviour rather than calling the API from a context means
  every existing Swoosh.Email in the app, including whatever a generator wrote,
  goes out over emails.sh with no other change.
  """

  use Swoosh.Adapter, required_config: [:api_key]

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

  @impl Swoosh.Adapter
  def deliver(%Swoosh.Email{} = email, config) do
    body =
      %{
        from: address(email.from),
        to: Enum.map(email.to, &address/1),
        subject: email.subject,
        html: email.html_body,
        text: email.text_body
      }
      |> put_unless_empty(:cc, Enum.map(email.cc || [], &address/1))
      |> put_unless_empty(:bcc, Enum.map(email.bcc || [], &address/1))
      |> put_unless_empty(:reply_to, email.reply_to && address(email.reply_to))
      |> Map.reject(fn {_key, value} -> is_nil(value) end)

    case Req.post(@endpoint,
           json: body,
           auth: {:bearer, Keyword.fetch!(config, :api_key)},
           receive_timeout: 15_000
         ) do
      {:ok, %{status: status, body: %{"id" => id}}} when status in 200..299 ->
        {:ok, %{id: id}}

      {:ok, %{status: status, body: body}} ->
        {:error, {status, body}}

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

  defp address({nil, email}), do: email
  defp address({name, email}), do: "#{name} <#{email}>"
  defp address(email) when is_binary(email), do: email

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

## The email and how you send it

lib/my_app/accounts/user_notifier.ex

```
defmodule MyApp.Accounts.UserNotifier do
  import Swoosh.Email

  alias MyApp.Mailer

  def deliver_welcome(user) do
    new()
    |> to({user.name, user.email})
    |> from({"Acme", "hello@acme.com"})
    |> subject("Welcome to Acme")
    |> html_body("<p>Hi #{Phoenix.HTML.html_escape(user.name) |> Phoenix.HTML.safe_to_string()}, your Acme account is ready.</p>")
    |> text_body("Hi #{user.name}, your Acme account is ready.")
    |> Mailer.deliver()
  end

  @doc """
  Sends without holding up the caller. Task.Supervisor is already in the
  supervision tree of a generated Phoenix app as MyApp.TaskSupervisor.
  """
  def deliver_welcome_async(user) do
    Task.Supervisor.start_child(MyApp.TaskSupervisor, fn -> deliver_welcome(user) end)
  end
end
```

## Worth knowing

### Read the key in runtime.exs, not config.exs

config.exs is evaluated when you compile, so a value read there is frozen into the release and the same for every environment. runtime.exs runs on boot, which is what you want for a secret.

### Keep the Local adapter in dev

Swoosh.Adapters.Local plus the mailbox preview at /dev/mailbox lets you see rendered mail without sending. Only prod needs the real adapter.

### Mailer.deliver blocks the caller

In a LiveView or controller that is a request the user is waiting on. Wrap it in Task.Supervisor.start_child, or use Oban if the send must survive a node restart.

### Swoosh addresses are tuples

{"Acme", "hello@acme.com"}, not a formatted string, which is why the adapter has an address/1 that turns both shapes into the "Name <addr>" the API expects.


## Questions

### How do I send email from Phoenix without SMTP?

Write a Swoosh adapter over the HTTP API, as above. No SMTP configuration, no connection pool, and it works on hosts that block outbound mail ports.

### Do I have to use Swoosh?

No. Req.post to https://emails.sh/v1/emails from a context module works. Swoosh is worth it because a generated Phoenix app already builds Swoosh.Email structs.

### How do I test emails in Phoenix?

Keep Swoosh.Adapters.Test in config/test.exs and use assert_email_sent/1 from Swoosh.TestAssertions. The adapter is never called in tests.


Docs: https://emails.sh/docs.md