defmodule FabricaMail do @moduledoc """ Drop-in Swoosh adapter for Fabrica Mail. Copy this file into your application's `lib/` directory. Requires the existing `:swoosh` and `:req` dependencies. config :my_app, MyApp.Mailer, adapter: FabricaMail, base_url: System.fetch_env!("FABRICA_URL"), token: System.fetch_env!("FABRICA_TOKEN") Use the Fabrica origin as `base_url`, without `/api/v1`. Supports to/cc/bcc, reply-to, HTML/text, custom headers and attachments (including inline images). `put_provider_option(:tag, "welcome")` and `put_provider_option(:metadata, %{})` are forwarded. Delivery is a single attempt: check Postmark before retrying an ambiguous timeout. `{:ok, %{id: id}}` means provider acceptance, not delivery. """ use Swoosh.Adapter, required_config: [:base_url, :token] @impl true def deliver(%Swoosh.Email{} = email, config) do payload = %{ from: address(email.from), to: Enum.map(email.to, &address/1), cc: Enum.map(email.cc, &address/1), bcc: Enum.map(email.bcc, &address/1), reply_to: address(email.reply_to), subject: email.subject, html_body: email.html_body, text_body: email.text_body, tag: email.provider_options[:tag], metadata: email.provider_options[:metadata], headers: Enum.map(email.headers, fn {name, value} -> %{name: name, value: value} end), attachments: Enum.map(email.attachments, &attachment/1) } |> Map.reject(fn {_, value} -> is_nil(value) end) options = [ url: String.trim_trailing(Keyword.fetch!(config, :base_url), "/") <> "/api/v1/emails", auth: {:bearer, Keyword.fetch!(config, :token)}, json: payload, retry: false, redirect: false, receive_timeout: 30_000 ] # :req_options supports Req.Test in the consuming application's tests. case Req.post(Keyword.merge(options, Keyword.get(config, :req_options, []))) do {:ok, %{status: 200, body: %{"message_id" => id, "status" => "accepted"}}} -> {:ok, %{id: id}} {:ok, %{status: status, body: body}} -> {:error, {status, body}} {:error, error} -> {:error, error} end end defp address(nil), do: nil defp address({"", email}), do: email defp address({name, email}) do # Quoted display names allow ordinary punctuation; Fabrica accepts a list # of individual addresses so commas within names aren't split. escaped = name |> String.replace("\\", "\\\\") |> String.replace("\"", "\\\"") "\"#{escaped}\" <#{email}>" end defp attachment(attachment) do %{ name: attachment.filename, content_type: attachment.content_type, content: Swoosh.Attachment.get_content(attachment, :base64) } |> then(fn item -> if attachment.type == :inline and attachment.cid, do: Map.put(item, :content_id, "cid:" <> String.trim_leading(attachment.cid, "cid:")), else: item end) end end