Packages

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.

Security advisory: This version has known vulnerabilities. View advisories

Current section

Files

Jump to
swoosh lib swoosh adapters brevo.ex
Raw

lib/swoosh/adapters/brevo.ex

defmodule Swoosh.Adapters.Brevo do
@moduledoc ~S"""
An adapter that sends email using the Brevo API (Transactional emails only).
For reference: [Brevo API docs](https://developers.brevo.com/reference/sendtransacemail)
**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.Brevo,
api_key: "my-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("nora@example.com")
|> to("shushu@example.com")
|> subject("Hello, Wally!")
|> text_body("Hello")
|> put_provider_option(:id, 42)
|> put_provider_option(:template_id, 42)
|> put_provider_option(:params, %{param1: "a", param2: 123})
|> put_provider_option(:tags, ["tag_1", "tag_2"])
|> put_provider_option(:schedule_at, ~U[2022-11-15 11:00:00Z])
## Batch Sending
This adapter supports `deliver_many/2` for sending multiple emails in a single API call
using Brevo's `messageVersions` parameter. When using batch sending:
**Global parameters** (applied to all emails in the batch):
* `sender` - from address (taken from first email)
* `attachment` - attachments (taken from first email)
* `tags` - email tags (taken from first email)
* `scheduledAt` - scheduled send time (taken from first email)
**Per-email parameters** (can be different for each email):
* `to`, `cc`, `bcc` - recipients
* `subject` - email subject
* `htmlContent`, `textContent` - email content
* `templateId` - template selection
* `params` - template variables
* `headers` - custom headers
* `replyTo` - reply address
## Provider Options
* `sender_id` (integer) - `sender`, the sender `id` where this library will
add email obtained from the `from/1`
* `template_id` (integer) - `templateId`, the Id of the `active`
transactional email template
* `params` (map) - `params`, a map of key/value attributes to customize the
template
* `tags` (list[string]) - `tags`, a list of tags for each email for easy
filtering
* `schedule_at` (UTC DateTime) - `schedule_at`, a UTC date-time on which the email has to schedule
"""
use Swoosh.Adapter, required_config: [:api_key]
alias Swoosh.Email
@base_url "https://api.brevo.com/v3"
@api_endpoint "/smtp/email"
defp base_url(config), do: config[:base_url] || @base_url
def deliver(%Email{} = email, config \\ []) do
headers = prepare_request_headers(config)
body = email |> prepare_payload() |> Swoosh.json_library().encode!
url = [base_url(config), @api_endpoint]
case Swoosh.ApiClient.post(url, headers, body, email) do
{:ok, code, _headers, body} when code >= 200 and code <= 399 ->
{:ok, %{id: extract_message_id(body)}}
{:ok, code, _headers, body} when code >= 400 ->
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, []}
end
def deliver_many([first_email | _] = emails, config) do
headers = prepare_request_headers(config)
body = emails |> prepare_batch_payload() |> Swoosh.json_library().encode!
url = [base_url(config), @api_endpoint]
case Swoosh.ApiClient.post(url, headers, body, first_email) do
{:ok, code, _headers, body} when code >= 200 and code <= 399 ->
{:ok, Enum.map(extract_message_ids(body), &%{id: &1})}
{:ok, code, _headers, body} when code >= 400 ->
case Swoosh.json_library().decode(body) do
{:ok, error} -> {:error, {code, error}}
{:error, _} -> {:error, {code, body}}
end
{:error, reason} ->
{:error, reason}
end
end
defp prepare_request_headers(config) do
[
{"Accept", "application/json"},
{"Content-Type", "application/json"},
{"User-Agent", "swoosh/#{Swoosh.version()}"},
{"Api-Key", config[:api_key]}
]
end
defp extract_message_id(body) do
body
|> Swoosh.json_library().decode!()
|> Map.get("messageId")
end
defp extract_message_ids(body) do
body
|> Swoosh.json_library().decode!()
|> Map.get("messageIds", [])
end
defp prepare_batch_payload([first_email | _] = emails) do
%{}
# from, attachments, tags and scheduled_at can only be defined globally
|> prepare_from(first_email)
|> prepare_attachments(first_email)
|> prepare_tags(first_email)
|> prepare_schedule_at(first_email)
# subject, text_content, html_content, template_id can be overridden per message but must be defined globally first
|> prepare_subject(first_email)
|> prepare_text_content(first_email)
|> prepare_html_content(first_email)
|> prepare_template_id(first_email)
|> Map.put("messageVersions", Enum.map(emails, &prepare_message_version/1))
end
defp prepare_message_version(email) do
%{}
|> prepare_reply_to(email)
|> prepare_to(email)
|> prepare_cc(email)
|> prepare_bcc(email)
|> prepare_subject(email)
|> prepare_text_content(email)
|> prepare_html_content(email)
|> prepare_template_id(email)
|> prepare_headers(email)
|> prepare_params(email)
end
defp prepare_payload(email) do
%{}
|> prepare_from(email)
|> prepare_reply_to(email)
|> prepare_to(email)
|> prepare_cc(email)
|> prepare_bcc(email)
|> prepare_subject(email)
|> prepare_text_content(email)
|> prepare_html_content(email)
|> prepare_template_id(email)
|> prepare_headers(email)
|> prepare_params(email)
|> prepare_tags(email)
|> prepare_attachments(email)
|> prepare_schedule_at(email)
end
defp prepare_from(payload, %{from: {_name, email}, provider_options: %{sender_id: sender_id}}),
do: Map.put(payload, "sender", %{id: sender_id, email: email})
defp prepare_from(payload, %{from: {_, "TEMPLATE"}}), do: payload
defp prepare_from(payload, %{from: from}),
do: Map.put(payload, "sender", prepare_recipient(from))
defp prepare_reply_to(payload, %{reply_to: nil}),
do: payload
defp prepare_reply_to(payload, %{reply_to: reply_to}),
do: Map.put(payload, "replyTo", prepare_recipient(reply_to))
defp prepare_to(payload, %{to: to}) do
Map.put(payload, "to", Enum.map(to, &prepare_recipient/1))
end
defp prepare_cc(payload, %{cc: []}), do: payload
defp prepare_cc(payload, %{cc: cc}) do
Map.put(payload, "cc", Enum.map(cc, &prepare_recipient/1))
end
defp prepare_bcc(payload, %{bcc: []}), do: payload
defp prepare_bcc(payload, %{bcc: bcc}) do
Map.put(payload, "bcc", Enum.map(bcc, &prepare_recipient/1))
end
defp prepare_subject(payload, %{subject: ""}), do: payload
defp prepare_subject(payload, %{subject: subject}),
do: Map.put(payload, "subject", subject)
defp prepare_subject(payload, _), do: payload
defp prepare_text_content(payload, %{text_body: nil}), do: payload
defp prepare_text_content(payload, %{text_body: text_content}) do
Map.put(payload, "textContent", text_content)
end
defp prepare_html_content(payload, %{html_body: nil}), do: payload
defp prepare_html_content(payload, %{html_body: html_content}) do
Map.put(payload, "htmlContent", html_content)
end
defp prepare_template_id(payload, %{provider_options: %{template_id: template_id}}) do
Map.put(payload, "templateId", template_id)
end
defp prepare_template_id(payload, _), do: payload
defp prepare_headers(payload, %{headers: map}) when map_size(map) == 0, do: payload
defp prepare_headers(payload, %{headers: headers}) do
Map.put(payload, "headers", headers)
end
defp prepare_params(payload, %{provider_options: %{params: params}}) when is_map(params) do
Map.put(payload, "params", params)
end
defp prepare_params(payload, _), do: payload
defp prepare_tags(payload, %{provider_options: %{tags: tags}}) when is_list(tags) do
Map.put(payload, "tags", tags)
end
defp prepare_tags(payload, _), do: payload
defp prepare_attachments(payload, %{attachments: []}), do: payload
defp prepare_attachments(payload, %{attachments: attachments}) do
Map.put(
payload,
"attachment",
Enum.map(
attachments,
&%{
name: &1.filename,
content: Swoosh.Attachment.get_content(&1, :base64)
}
)
)
end
defp prepare_schedule_at(payload, %{provider_options: %{schedule_at: schedule_at}}) do
Map.put(payload, "scheduledAt", schedule_at)
end
defp prepare_schedule_at(payload, _), do: payload
defp prepare_recipient({name, email}) when name in [nil, ""],
do: %{email: email}
defp prepare_recipient({name, email}),
do: %{name: name, email: email}
end