Packages
swoosh
1.26.3
1.26.3
1.26.2
1.26.1
1.26.0
1.25.3
1.25.2
1.25.1
1.25.0
1.24.0
1.23.1
1.23.0
1.22.1
1.22.0
1.21.0
1.20.1
1.20.0
1.19.9
1.19.8
1.19.7
1.19.6
1.19.5
1.19.4
1.19.3
1.19.2
1.19.1
1.19.0
1.18.4
1.18.3
1.18.2
1.18.1
1.18.0
1.17.10
1.17.9
1.17.8
retired
1.17.7
retired
1.17.6
1.17.5
1.17.4
retired
1.17.3
1.17.2
1.17.1
1.17.0
1.16.12
1.16.11
1.16.10
1.16.9
1.16.8
1.16.7
1.16.6
1.16.5
1.16.4
1.16.3
1.16.2
retired
1.16.1
retired
1.16.0
1.15.3
1.15.2
1.15.1
1.15.0
1.14.4
1.14.3
1.14.2
1.14.1
1.14.0
1.13.0
1.12.0
1.11.6
1.11.5
1.11.4
1.11.3
1.11.2
1.11.1
retired
1.11.0
1.10.3
1.10.2
1.10.1
1.10.0
1.9.1
1.9.0
1.8.3
1.8.2
1.8.1
1.8.0
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.2
1.5.1
1.5.0
1.4.0
1.3.11
1.3.10
1.3.9
1.3.8
retired
1.3.7
1.3.6
retired
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.2
1.2.1
1.2.0
1.1.2
1.1.1
1.1.0
1.0.9
1.0.8
retired
1.0.7
retired
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.25.6
0.25.5
0.25.4
0.25.3
retired
0.25.2
0.25.1
0.25.0
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.5
0.23.4
0.23.3
0.23.2
0.23.1
0.23.0
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.1
0.16.0
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.0
0.10.0
0.9.1
0.9.0
0.8.1
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Compose, deliver and test your emails easily in Elixir. Supports SMTP, Sendgrid, Mandrill, Postmark, Mailgun and many more out of the box. Preview your emails in the browser. Test your email sending code.
Current section
Files
Jump to
Current section
Files
lib/swoosh/adapters/mailersend.ex
defmodule Swoosh.Adapters.Mailersend do
@moduledoc ~S"""
An adapter that sends email using the MailerSend API.
For reference: [MailerSend API docs](https://developers.mailersend.com/api/v1/email.html)
**This adapter requires an API Client.** Swoosh comes with Hackney, Finch and Req out of the box.
See the [installation section](https://hexdocs.pm/swoosh/Swoosh.html#module-installation)
for details.
## Example
# config/config.exs
config :sample, Sample.Mailer,
adapter: Swoosh.Adapters.Mailersend,
api_key: "your-api-key"
# lib/sample/mailer.ex
defmodule Sample.Mailer do
use Swoosh.Mailer, otp_app: :sample
end
## Using with provider options
import Swoosh.Email
new()
|> from({"T Stark", "tony.stark@example.com"})
|> to({"Steve Rogers", "steve.rogers@example.com"})
|> subject("Hello, Avengers!")
|> html_body("<h1>Hello</h1>")
|> text_body("Hello")
|> put_provider_option(:tags, ["onboarding", "welcome"])
|> put_provider_option(:track_opens, true)
|> put_provider_option(:track_clicks, true)
|> put_provider_option(:metadata, %{"user_id" => "123"})
## Using with MailerSend templates
import Swoosh.Email
new()
|> from({"T Stark", "tony.stark@example.com"})
|> to({"Steve Rogers", "steve.rogers@example.com"})
|> put_provider_option(:template_id, "template-123")
|> put_provider_option(:template_variables, %{
"name" => "Steve",
"mission" => "Project Insight"
})
## Personalization without templates
MailerSend supports `{{ variable }}` substitution in subject, HTML, and
text body fields without requiring a template.
import Swoosh.Email
new()
|> from({"T Stark", "tony.stark@example.com"})
|> to({"Steve Rogers", "steve.rogers@example.com"})
|> subject("Welcome {{ name }}!")
|> html_body("<h1>Hello {{ name }}</h1>")
|> put_provider_option(:personalization, [
%{"email" => "steve.rogers@example.com", "data" => %{"name" => "Steve"}}
])
## Provider Options
* `:template_id` (string) - MailerSend template ID to use instead of
html/text body
* `:template_variables` (map) - variables for template personalization,
applied to all recipients via the `personalization` array
* `:personalization` (list of maps) - per-recipient personalization data
for non-template emails. Each map must have `"email"` and `"data"` keys.
Values populate `{{ variable }}` placeholders in subject, HTML, and text
* `:tags` (list of strings) - tags to categorize the email (max 5)
* `:webhook_id` (string) - webhook ID for delivery tracking
* `:metadata` (map) - custom metadata to attach to the email
* `:send_at` (integer or DateTime) - unix timestamp or DateTime for
scheduled sending
* `:track_opens` (boolean) - enable open tracking
* `:track_clicks` (boolean) - enable click tracking
* `:track_content` (boolean) - enable content tracking
* `:in_reply_to` (string) - Message-ID of the email being replied to
* `:references` (list of strings) - list of Message-IDs that the
current email is referencing
* `:precedence_bulk` (boolean) - set precedence bulk header, overrides
domain's advanced settings
* `:list_unsubscribe` (string) - List-Unsubscribe header value per
RFC 8058
"""
use Swoosh.Adapter, required_config: [:api_key]
alias Swoosh.Email
@base_url "https://api.mailersend.com"
@api_endpoint "/v1/email"
@bulk_endpoint "/v1/bulk-email"
def deliver(%Email{} = email, config \\ []) do
headers = prepare_headers(config)
url = [base_url(config), @api_endpoint]
body = email |> prepare_body() |> Swoosh.json_library().encode!()
case Swoosh.ApiClient.post(url, headers, body, email) do
{:ok, 202, response_headers, response_body} ->
id = get_header_value(response_headers, "x-message-id")
case Swoosh.json_library().decode(response_body) do
{:ok, %{"warnings" => warnings}} when is_list(warnings) ->
{:ok, %{id: id, warnings: warnings}}
_ ->
{:ok, %{id: id}}
end
{:ok, code, _headers, body} when code > 399 ->
case Swoosh.json_library().decode(body) do
{:ok, error} -> {:error, {code, error}}
{:error, _} -> {:error, {code, body}}
end
{:error, reason} ->
{:error, reason}
end
end
def deliver_many(emails, config \\ [])
def deliver_many([], _config), do: {:ok, []}
def deliver_many(emails, config) when is_list(emails) do
headers = prepare_headers(config)
body = emails |> Enum.map(&prepare_body/1) |> Swoosh.json_library().encode!()
url = [base_url(config), @bulk_endpoint]
case Swoosh.ApiClient.post(url, headers, body, List.first(emails)) do
{:ok, 202, _headers, body} ->
{:ok, [%{bulk_email_id: extract_bulk_id(body)}]}
{:ok, code, _headers, body} when code > 399 ->
case Swoosh.json_library().decode(body) do
{:ok, error} -> {:error, {code, error}}
{:error, _} -> {:error, {code, body}}
end
{:error, reason} ->
{:error, reason}
end
end
defp extract_bulk_id(body) do
body |> Swoosh.json_library().decode!() |> Map.get("bulk_email_id")
end
defp base_url(config), do: config[:base_url] || @base_url
defp prepare_headers(config) do
[
{"User-Agent", "swoosh/#{Swoosh.version()}"},
{"Authorization", "Bearer #{config[:api_key]}"},
{"Content-Type", "application/json"},
{"Accept", "application/json"}
]
end
defp prepare_body(%Email{provider_options: %{template_id: template_id}} = email) do
%{}
|> Map.put("template_id", template_id)
|> prepare_from(email)
|> prepare_to(email)
|> prepare_cc(email)
|> prepare_bcc(email)
|> prepare_subject_if_present(email)
|> prepare_reply_to(email)
|> prepare_attachments(email)
|> prepare_custom_headers(email)
|> prepare_template_personalization(email)
|> prepare_tags(email)
|> prepare_webhook_id(email)
|> prepare_metadata(email)
|> prepare_send_at(email)
|> prepare_tracking(email)
|> prepare_in_reply_to(email)
|> prepare_references(email)
|> prepare_precedence_bulk(email)
|> prepare_list_unsubscribe(email)
end
defp prepare_body(email) do
%{}
|> prepare_from(email)
|> prepare_to(email)
|> prepare_cc(email)
|> prepare_bcc(email)
|> prepare_subject(email)
|> prepare_html(email)
|> prepare_text(email)
|> prepare_reply_to(email)
|> prepare_attachments(email)
|> prepare_custom_headers(email)
|> prepare_personalization(email)
|> prepare_tags(email)
|> prepare_webhook_id(email)
|> prepare_metadata(email)
|> prepare_send_at(email)
|> prepare_tracking(email)
|> prepare_in_reply_to(email)
|> prepare_references(email)
|> prepare_precedence_bulk(email)
|> prepare_list_unsubscribe(email)
end
defp prepare_from(body, %{from: nil}), do: body
defp prepare_from(body, %{from: from}), do: Map.put(body, "from", render_recipient(from))
defp prepare_to(body, %{to: []}), do: body
defp prepare_to(body, %{to: to}), do: Map.put(body, "to", Enum.map(to, &render_recipient/1))
defp prepare_cc(body, %{cc: []}), do: body
defp prepare_cc(body, %{cc: cc}), do: Map.put(body, "cc", Enum.map(cc, &render_recipient/1))
defp prepare_bcc(body, %{bcc: []}), do: body
defp prepare_bcc(body, %{bcc: bcc}),
do: Map.put(body, "bcc", Enum.map(bcc, &render_recipient/1))
defp prepare_subject(body, %{subject: nil}), do: body
defp prepare_subject(body, %{subject: subject}), do: Map.put(body, "subject", subject)
defp prepare_subject_if_present(body, %{subject: nil}), do: body
defp prepare_subject_if_present(body, %{subject: ""}), do: body
defp prepare_subject_if_present(body, %{subject: subject}),
do: Map.put(body, "subject", subject)
defp prepare_html(body, %{html_body: nil}), do: body
defp prepare_html(body, %{html_body: html_body}), do: Map.put(body, "html", html_body)
defp prepare_text(body, %{text_body: nil}), do: body
defp prepare_text(body, %{text_body: text_body}), do: Map.put(body, "text", text_body)
defp prepare_reply_to(body, %{reply_to: nil}), do: body
defp prepare_reply_to(body, %{reply_to: reply_to}),
do: Map.put(body, "reply_to", render_recipient(reply_to))
defp prepare_attachments(body, %{attachments: []}), do: body
defp prepare_attachments(body, %{attachments: attachments}) do
sorted =
Enum.sort_by(attachments, fn
%{type: :inline} -> 1
_ -> 0
end)
Map.put(body, "attachments", Enum.map(sorted, &prepare_attachment/1))
end
defp prepare_attachment(attachment) do
base = %{
"content" => Swoosh.Attachment.get_content(attachment, :base64),
"filename" => attachment.filename,
"type" => attachment.content_type
}
case attachment.type do
:inline ->
base
|> Map.put("disposition", "inline")
|> put_attachment_id(attachment.cid)
_ ->
Map.put(base, "disposition", "attachment")
end
end
defp put_attachment_id(att, cid) when is_binary(cid), do: Map.put(att, "id", cid)
defp put_attachment_id(att, _), do: att
defp prepare_custom_headers(body, %{headers: headers}) when map_size(headers) == 0, do: body
defp prepare_custom_headers(body, %{headers: headers}) do
formatted = Enum.map(headers, fn {name, value} -> %{"name" => name, "value" => value} end)
Map.put(body, "headers", formatted)
end
# Template personalization: applies template_variables to all recipients
defp prepare_template_personalization(body, %{
to: to,
cc: cc,
bcc: bcc,
provider_options: provider_options
}) do
case Map.get(provider_options, :template_variables) do
nil ->
body
variables when is_map(variables) ->
all_recipients = to ++ cc ++ bcc
personalization =
Enum.map(all_recipients, fn recipient ->
%{
"email" => recipient_email(recipient),
"data" => variables
}
end)
Map.put(body, "personalization", personalization)
end
end
# Non-template personalization: user provides per-recipient data directly
defp prepare_personalization(body, %{provider_options: %{personalization: personalization}})
when is_list(personalization) do
Map.put(body, "personalization", personalization)
end
defp prepare_personalization(body, _email), do: body
defp prepare_tags(body, %{provider_options: %{tags: tags}}) when is_list(tags),
do: Map.put(body, "tags", tags)
defp prepare_tags(body, _), do: body
defp prepare_webhook_id(body, %{provider_options: %{webhook_id: id}}),
do: Map.put(body, "webhook_id", id)
defp prepare_webhook_id(body, _), do: body
defp prepare_metadata(body, %{provider_options: %{metadata: metadata}}) when is_map(metadata),
do: Map.put(body, "metadata", metadata)
defp prepare_metadata(body, _), do: body
defp prepare_send_at(body, %{provider_options: %{send_at: %DateTime{} = dt}}),
do: Map.put(body, "send_at", DateTime.to_unix(dt))
defp prepare_send_at(body, %{provider_options: %{send_at: ts}}) when is_integer(ts),
do: Map.put(body, "send_at", ts)
defp prepare_send_at(body, _), do: body
defp prepare_tracking(body, %{provider_options: provider_options}) do
settings =
%{}
|> put_if_boolean("track_opens", Map.get(provider_options, :track_opens))
|> put_if_boolean("track_clicks", Map.get(provider_options, :track_clicks))
|> put_if_boolean("track_content", Map.get(provider_options, :track_content))
case map_size(settings) do
0 -> body
_ -> Map.put(body, "settings", settings)
end
end
defp prepare_tracking(body, _), do: body
defp prepare_in_reply_to(body, %{provider_options: %{in_reply_to: value}})
when is_binary(value),
do: Map.put(body, "in_reply_to", value)
defp prepare_in_reply_to(body, _), do: body
defp prepare_references(body, %{provider_options: %{references: refs}}) when is_list(refs),
do: Map.put(body, "references", refs)
defp prepare_references(body, _), do: body
defp prepare_precedence_bulk(body, %{provider_options: %{precedence_bulk: value}})
when is_boolean(value),
do: Map.put(body, "precedence_bulk", value)
defp prepare_precedence_bulk(body, _), do: body
defp prepare_list_unsubscribe(body, %{provider_options: %{list_unsubscribe: value}})
when is_binary(value),
do: Map.put(body, "list_unsubscribe", value)
defp prepare_list_unsubscribe(body, _), do: body
defp put_if_boolean(map, _key, nil), do: map
defp put_if_boolean(map, key, value) when is_boolean(value), do: Map.put(map, key, value)
defp put_if_boolean(map, _key, _), do: map
defp render_recipient({name, email}) when is_binary(name) and name != "",
do: %{"email" => email, "name" => name}
defp render_recipient({_name, email}), do: %{"email" => email}
defp render_recipient(email) when is_binary(email), do: %{"email" => email}
defp recipient_email({_name, email}), do: email
defp recipient_email(email) when is_binary(email), do: email
defp get_header_value(headers, key) do
key_down = String.downcase(key)
Enum.find_value(headers, fn {k, v} ->
if String.downcase(k) == key_down, do: v
end)
end
end