Packages
swoosh
1.26.0
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.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/swoosh/adapters/smtp/helpers.ex
defmodule Swoosh.Adapters.SMTP.Helpers do
@moduledoc false
alias Swoosh.Email
import Swoosh.Email.Render
@doc false
def sender(%Email{} = email) do
email.headers["Sender"] || elem(email.from, 1)
end
@doc false
def body(email, config) do
{message_config, config} = Keyword.split(config, [:transfer_encoding, :keep_bcc])
{type, subtype, headers, parts} = prepare_message(email, message_config)
{encoding_config, _config} = Keyword.split(config, [:dkim])
mime_encode(type, subtype, headers, parts, encoding_config)
end
# TODO: Remove conditional handling when going 2.0
gen_smtp_major =
if Code.ensure_loaded?(:gen_smtp_client) do
Application.load(:gen_smtp)
:gen_smtp
|> Application.spec(:vsn)
|> to_string()
|> Version.parse!()
|> Map.get(:major)
else
0
end
@parameters if(gen_smtp_major >= 1, do: %{}, else: [])
defp mime_encode(type, subtype, headers, parts, encoding_config) do
:mimemail.encode({type, subtype, headers, @parameters, parts}, encoding_config)
end
@doc false
def prepare_message(email, config) do
email
|> prepare_headers(config)
|> prepare_parts(email, config)
end
defp prepare_headers(email, config) do
[]
|> prepare_additional_headers(email)
|> prepare_mime_version()
|> prepare_reply_to(email)
|> prepare_subject(email)
|> prepare_cc(email)
|> then(fn headers ->
# bcc is deliberately omitted unless explicitly asked to keep, for service providers
# https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
if config[:keep_bcc] do
prepare_bcc(headers, email)
else
headers
end
end)
|> prepare_to(email)
|> prepare_from(email)
end
defp prepare_subject(headers, %{subject: subject}) when is_binary(subject),
do: [{"Subject", subject} | headers]
defp prepare_from(headers, %{from: from}), do: [{"From", render_recipient(from)} | headers]
defp prepare_to(headers, %{to: []}), do: headers
defp prepare_to(headers, %{to: to}), do: [{"To", render_recipient(to)} | headers]
defp prepare_cc(headers, %{cc: []}), do: headers
defp prepare_cc(headers, %{cc: cc}), do: [{"Cc", render_recipient(cc)} | headers]
defp prepare_bcc(headers, %{bcc: []}), do: headers
defp prepare_bcc(headers, %{bcc: bcc}), do: [{"Bcc", render_recipient(bcc)} | headers]
defp prepare_reply_to(headers, %{reply_to: nil}), do: headers
defp prepare_reply_to(headers, %{reply_to: reply_to}),
do: [{"Reply-To", render_recipient(reply_to)} | headers]
defp prepare_mime_version(headers), do: [{"MIME-Version", "1.0"} | headers]
defp prepare_additional_headers(headers, %{headers: additional_headers}) do
Map.to_list(additional_headers) ++ headers
end
defp prepare_parts(
headers,
%{
attachments: [],
html_body: html_body,
text_body: text_body
},
config
) do
case {text_body, html_body} do
{text_body, nil} ->
{"text", "plain", add_content_type_header(headers, "text/plain; charset=\"utf-8\""),
text_body}
{nil, html_body} ->
{"text", "html", add_content_type_header(headers, "text/html; charset=\"utf-8\""),
html_body}
{text_body, html_body} ->
parts = [
prepare_part(:plain, text_body, config),
prepare_part(:html, html_body, config)
]
{"multipart", "alternative", headers, parts}
end
end
defp prepare_parts(
headers,
%{
attachments: attachments,
html_body: html_body,
text_body: text_body
},
config
) do
{inline_attachments, attachments} = Enum.split_with(attachments, &(&1.type == :inline))
content_part =
case {prepare_part(:plain, text_body, config), prepare_part(:html, html_body, config)} do
{text_part, nil} ->
text_part
{nil, html_part} ->
html_with_line_attachments(html_part, inline_attachments)
{text_part, html_part} ->
html_part = html_with_line_attachments(html_part, inline_attachments)
{"multipart", "alternative", [], %{}, [text_part, html_part]}
end
attachment_parts = Enum.map(attachments, &prepare_attachment(&1))
{"multipart", "mixed", headers, [content_part | attachment_parts]}
end
defp prepare_part(_subtype, nil, _config), do: nil
@content_params if(gen_smtp_major >= 1,
do: %{
content_type_params: [{"charset", "utf-8"}],
disposition: "inline",
disposition_params: []
},
else: [
{"content-type-params", [{"charset", "utf-8"}]},
{"disposition", "inline"},
{"disposition-params", []}
]
)
defp prepare_part(subtype, content, config) do
subtype_string = to_string(subtype)
transfer_encoding = Keyword.get(config, :transfer_encoding, "quoted-printable")
{"text", subtype_string,
[
{"Content-Type", "text/#{subtype_string}; charset=\"utf-8\""},
{"Content-Transfer-Encoding", transfer_encoding}
], @content_params, content}
end
defp html_with_line_attachments(html_part, []), do: html_part
defp html_with_line_attachments(html_part, inline_attachments) do
attachment_parts = Enum.map(inline_attachments, &prepare_attachment(&1))
{"multipart", "related", [], %{}, [html_part | attachment_parts]}
end
defp add_content_type_header(headers, value) do
if Enum.find(headers, fn {header_name, _} ->
String.downcase(header_name) == "content-type"
end) do
headers
else
[{"Content-Type", value} | headers]
end
end
defp prepare_attachment(
%Swoosh.Attachment{
cid: cid,
filename: filename,
content_type: content_type,
type: attachment_type,
headers: custom_headers
} = attachment
) do
[type, format] = String.split(content_type, "/")
content = Swoosh.Attachment.get_content(attachment)
transfer_encoding = transfer_encoding_for_content_type(content_type)
case attachment_type do
:attachment ->
{
type,
format,
[
{"Content-Transfer-Encoding", transfer_encoding}
| custom_headers
],
attachment_content_params(:attachment, filename),
content
}
:inline ->
{
type,
format,
[
{"Content-Transfer-Encoding", transfer_encoding},
{"Content-Id", "<#{cid || filename}>"}
| custom_headers
],
attachment_content_params(:inline, filename),
content
}
end
end
# RFC 2046 Section 5.2: message/* types must not use base64 encoding
# - message/rfc822 allows "7bit", "8bit", or "binary" (Section 5.2.1)
# - message/partial must be "7bit" (Section 5.2.2)
# - message/external-body must be "7bit" (Section 5.2.3)
defp transfer_encoding_for_content_type("message/rfc822"), do: "8bit"
defp transfer_encoding_for_content_type("message/" <> _), do: "7bit"
defp transfer_encoding_for_content_type(_), do: "base64"
if gen_smtp_major >= 1 do
defp attachment_content_params(:attachment, filename) do
%{
disposition: "attachment",
disposition_params: [{"filename", filename}]
}
end
else
defp attachment_content_params(:attachment, filename) do
[
{"disposition", "attachment"},
{"disposition-params", [{"filename", filename}]}
]
end
end
if gen_smtp_major >= 1 do
defp attachment_content_params(:inline, filename) do
%{
content_type_params: [],
disposition: "inline",
disposition_params: [{"filename", filename}]
}
end
else
defp attachment_content_params(:inline, filename) do
[
{"content-type-params", []},
{"disposition", "inline"},
{"disposition-params", [{"filename", filename}]}
]
end
end
end