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 postmark.ex
Raw

lib/swoosh/adapters/postmark.ex

defmodule Swoosh.Adapters.Postmark do
@moduledoc ~S"""
An adapter that sends email using the Postmark API.
For reference: [Postmark API docs](http://developer.postmarkapp.com/developer-send-api.html)
## Example
# config/config.exs
config :sample, Sample.Mailer,
adapter: Swoosh.Adapters.Postmark,
api_key: "my-api-key"
# lib/sample/mailer.ex
defmodule Sample.Mailer do
use Swoosh.Mailer, otp_app: :sample
end
## Example of sending emails using templates
This will use Postmark's `withTemplate` endpoint.
import Swoosh.Email
new()
|> from({"T Stark", "tony.stark@example.com"})
|> to({"Steve Rogers", "steve.rogers@example.com"})
|> put_provider_option(:template_id, "123456")
|> put_provider_option(:template_model, %{name: "Steve", email: "steve@avengers.com"})
You can also use `template_alias` instead of `template_id`, if you use Postmark's
[TemplateAlias](https://postmarkapp.com/developer/api/templates-api#email-with-template) feature.
When sending batch emails using `:deliver_many` do not mix emails using
templates with non-template emails. The use of templates impacts the API
endpoint used and so the batch email collection should be of the same format.
## Example of sending emails with a tag
This will add a tag to the sent Postmark's email.
import Swoosh.Email
new()
|> from({"T Stark", "tony.stark@example.com"})
|> to({"Steve Rogers", "steve.rogers@example.com"})
|> subject("Hello, Avengers!")
|> put_provider_option(:tag, "some tag")
## Provider Options
* `:message_stream` (string) – `MessageStream`, configure the message stream
for the email
* `:metadata` (map) - `Metadata`, add metadata to an email
* `:tag` (string) - `Tag`, to categorize outgoing email
* `:template_id` (string) - `TemplateId`, the template used when sending
email and only required if `:template_alias` is not specified
* `:template_alias` (string), `TemplateAlias`, the alias of a template used
when sending email and only required if `:template_id` is not specified
* `:template_model` (map), `TemplateModel`, a map of key/value field to be
used in the `HtmlBody`, `TextBody`, and `Subject` field in the template
* `:track_opens` (boolean) - `TrackOpens`, specify if open tracking needs to be enabled for this email.
* `:track_links` (string) - `TrackOpens`, specify if link tracking needs to be enabled for this email.
Valid values are: `None`, `HtmlAndText`, `HtmlOnly`, `TextOnly`
* `:inline_css` (boolean) - `InlineCss`, specify if Postmark should apply the style blocks as inline
attributes to the rendered HTML content. Default is true.
"""
use Swoosh.Adapter, required_config: [:api_key]
alias Swoosh.Email
import Swoosh.Email.Render
@base_url "https://api.postmarkapp.com"
def deliver(%Email{} = email, config \\ []) do
headers = prepare_headers(config)
params = email |> prepare_body() |> Swoosh.json_library().encode!
url = [base_url(config), api_endpoint(email)]
case Swoosh.ApiClient.post(url, headers, params, email) do
{:ok, 200, _headers, body} ->
{:ok, %{id: Swoosh.json_library().decode!(body)["MessageID"]}}
{: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, []}
end
def deliver_many(emails, config) when is_list(emails) do
headers = prepare_headers(config)
body = emails |> prepare_body() |> Swoosh.json_library().encode!
url = [base_url(config), api_endpoint(List.first(emails), true)]
case Swoosh.ApiClient.post(url, headers, body, List.first(emails)) do
{:ok, 200, _headers, body} ->
results =
Enum.map(Swoosh.json_library().decode!(body), fn email_result ->
%{
id: email_result["MessageID"],
error_code: email_result["ErrorCode"],
message: email_result["Message"],
to: email_result["To"],
submitted_at: email_result["SubmittedAt"]
}
end)
{:ok, results}
{: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 base_url(config), do: config[:base_url] || @base_url
defp prepare_headers(config) do
[
{"User-Agent", "swoosh/#{Swoosh.version()}"},
{"X-Postmark-Server-Token", config[:api_key]},
{"Content-Type", "application/json"},
{"Accept", "application/json"}
]
end
defp api_endpoint(email, batch \\ false) do
case {email_uses_template?(email), batch} do
{true, true} -> "/email/batchWithTemplates"
{true, false} -> "/email/withTemplate"
{false, true} -> "/email/batch"
{false, false} -> "/email"
end
end
defp email_uses_template?(%Email{} = email) do
case email do
%{provider_options: %{template_id: _, template_model: _}} -> true
%{provider_options: %{template_alias: _, template_model: _}} -> true
_ -> false
end
end
defp prepare_body(emails) when is_list(emails) do
if email_uses_template?(List.first(emails)) do
%{"Messages" => Enum.map(emails, &prepare_body/1)}
else
Enum.map(emails, &prepare_body/1)
end
end
defp prepare_body(email) do
%{}
|> prepare_from(email)
|> prepare_to(email)
|> prepare_subject(email)
|> prepare_html(email)
|> prepare_text(email)
|> prepare_cc(email)
|> prepare_bcc(email)
|> prepare_attachments(email)
|> prepare_reply_to(email)
|> prepare_template(email)
|> prepare_custom_headers(email)
|> prepare_tag(email)
|> prepare_metadata(email)
|> prepare_message_stream(email)
|> prepare_track_opens(email)
|> prepare_track_links(email)
|> prepare_inline_css(email)
end
defp prepare_from(body, %{from: from}), do: Map.put(body, "From", render_recipient(from))
defp prepare_to(body, %{to: to}), do: Map.put(body, "To", render_recipient(to))
defp prepare_cc(body, %{cc: []}), do: body
defp prepare_cc(body, %{cc: cc}), do: Map.put(body, "Cc", render_recipient(cc))
defp prepare_bcc(body, %{bcc: []}), do: body
defp prepare_bcc(body, %{bcc: bcc}), do: Map.put(body, "Bcc", render_recipient(bcc))
defp prepare_attachments(body, %{attachments: []}), do: body
defp prepare_attachments(body, %{attachments: attachments}) do
Map.put(body, "Attachments", Enum.map(attachments, &prepare_attachment/1))
end
defp prepare_attachment(attachment) do
attachment_data = %{
"Name" => attachment.filename,
"ContentType" => attachment.content_type,
"Content" => Swoosh.Attachment.get_content(attachment, :base64)
}
case attachment.type do
:attachment ->
attachment_data
:inline ->
Map.put(attachment_data, "ContentID", "cid:#{attachment.cid}")
end
end
defp prepare_reply_to(body, %{reply_to: nil}), do: body
defp prepare_reply_to(body, %{reply_to: {_name, address}}),
do: Map.put(body, "ReplyTo", address)
defp prepare_subject(body, %{subject: ""}), do: body
defp prepare_subject(body, %{subject: subject}), do: Map.put(body, "Subject", subject)
defp prepare_text(body, %{text_body: nil}), do: body
defp prepare_text(body, %{text_body: text_body}), do: Map.put(body, "TextBody", text_body)
defp prepare_html(body, %{html_body: nil}), do: body
defp prepare_html(body, %{html_body: html_body}), do: Map.put(body, "HtmlBody", html_body)
# example custom vars
#
# %{
# "template_id" => 123,
# "template_model" => %{"name": 1, "company": 2}
# }
#
# Or, using template_alias
#
# %{
# "template_alias" => "welcome",
# "template_model" => %{"name": 1, "company": 2}
# }
defp prepare_template(body, %{provider_options: provider_options}),
do: Enum.reduce(provider_options, body, &put_in_body/2)
defp prepare_template(body, _email), do: body
defp put_in_body({:template_model, val}, body_acc), do: Map.put(body_acc, "TemplateModel", val)
defp put_in_body({:template_id, val}, body_acc), do: Map.put(body_acc, "TemplateId", val)
defp put_in_body({:template_alias, val}, body_acc), do: Map.put(body_acc, "TemplateAlias", val)
defp put_in_body(_, body_acc), do: body_acc
defp prepare_custom_headers(body, %{headers: headers}) when map_size(headers) == 0, do: body
defp prepare_custom_headers(body, %{headers: headers}) do
custom_headers = Enum.map(headers, fn {k, v} -> %{"Name" => k, "Value" => v} end)
Map.put(body, "Headers", custom_headers)
end
defp prepare_tag(body, %{provider_options: %{tag: tag_value}}) do
Map.put(body, "Tag", tag_value)
end
defp prepare_tag(body, _), do: body
defp prepare_metadata(body, %{provider_options: %{metadata: metadata}}) do
Map.put(body, "Metadata", metadata)
end
defp prepare_metadata(body, _), do: body
defp prepare_message_stream(body, %{provider_options: %{message_stream: value}}),
do: Map.put(body, "MessageStream", value)
defp prepare_message_stream(body, _), do: body
defp prepare_track_opens(body, %{provider_options: %{track_opens: value}}),
do: Map.put(body, "TrackOpens", value)
defp prepare_track_opens(body, _), do: body
defp prepare_track_links(body, %{provider_options: %{track_links: value}}),
do: Map.put(body, "TrackLinks", value)
defp prepare_track_links(body, _), do: body
defp prepare_inline_css(body, %{provider_options: %{inline_css: value}}),
do: Map.put(body, "InlineCss", value)
defp prepare_inline_css(body, _), do: body
end