defmodule BambooSes.Render.Raw do
@moduledoc """
Functions for rendering email messages into strings.
"""
@doc """
Returns a tuple with all data needed for the underlying adapter to send.
"""
def render(email, extra_headers \\ []) do
email
# Returns a list of tuples
|> compile_parts()
# Nests the tuples and attaches necessary metadata
|> nest_parts(email, extra_headers)
|> :mimemail.encode()
end
defp nest_parts(parts, email, extra_headers) do
{top_mime_type, top_mime_sub_type, _, _, top_content_part} = nested_content_part_tuples(parts)
{
top_mime_type,
top_mime_sub_type,
headers_for(email) ++ extra_headers,
%{},
top_content_part
}
end
defp nested_content_part_tuples(parts) do
plain_part_tuple = body_part_tuple(parts, :plain)
html_part_tuple = body_part_tuple(parts, :html)
# attachment_part_tuples(parts)
inline_attachment_part_tuples = []
attached_attachment_part_tuples = attachment_part_tuples(parts)
related_or_html_part_tuple =
if Enum.empty?(inline_attachment_part_tuples) do
html_part_tuple
else
if is_nil(html_part_tuple),
do: nil,
else:
{"multipart", "related", [], %{}, [html_part_tuple | inline_attachment_part_tuples]}
end
alternative_or_plain_tuple =
if is_nil(related_or_html_part_tuple) do
plain_part_tuple
else
{"multipart", "alternative", [], %{}, [plain_part_tuple, related_or_html_part_tuple]}
end
mixed_or_alternative_tuple =
if Enum.empty?(attached_attachment_part_tuples) do
alternative_or_plain_tuple
else
if is_nil(alternative_or_plain_tuple),
do: nil,
else:
{"multipart", "mixed", [], %{},
[alternative_or_plain_tuple | attached_attachment_part_tuples]}
end
mixed_or_alternative_tuple
end
@spec body_part_tuple([tuple()], atom()) :: nil | tuple()
defp body_part_tuple(parts, type) do
part = Enum.find(parts, &(elem(&1, 0) == type))
if is_nil(part) do
nil
else
{
mime_type_for(part),
mime_subtype_for(part),
headers_for(part),
parameters_for(part),
elem(part, 1)
}
end
end
@spec attachment_part_tuples([tuple()]) :: list(tuple())
defp attachment_part_tuples(parts) do
parts
|> Enum.filter(fn
{_, _, attachment} -> !is_nil(attachment)
_ -> false
end)
|> Enum.map(fn part ->
{
mime_type_for(part),
mime_subtype_for(part),
headers_for(part),
parameters_for(part),
elem(part, 1)
}
end)
end
defp mime_type_for({_type, _}) do
"text"
end
defp mime_type_for({_, _, attachment}) do
attachment.content_type
|> String.split("/")
|> List.first()
end
defp mime_subtype_for({type, _}) do
type
end
defp mime_subtype_for({_, _, attachment}) do
attachment.content_type
|> String.split("/")
|> List.last()
end
defp parameters_for({:attachment, _body, attachment}) do
%{
transfer_encoding: "base64",
content_type_params: [],
disposition: "attachment",
disposition_params: [{"filename", attachment.filename}]
}
end
defp parameters_for(_part) do
%{
transfer_encoding: "quoted-printable",
content_type_params: [],
disposition: "inline",
disposition_params: []
}
end
defp headers_for({:plain, _body}), do: []
defp headers_for({:html, _body}), do: []
defp headers_for({:attachment, _body, %{disposition: "inline"} = attachment}) do
attachment_id = URI.encode(attachment.file_name)
[
{"Content-ID", "<#{attachment_id}@bamboo_ses.attachment>"},
{"X-Attachment-Id", attachment_id}
]
end
defp headers_for({:attachment, _body, attachment}) do
if attachment.content_id do
[
{"Content-ID", attachment.content_id}
]
else
[]
end
end
defp headers_for(email) do
headers =
[
{"From", mailbox(email.from)},
{"Subject", email.subject}
] ++ Map.to_list(email.headers)
Enum.filter(headers, fn i -> elem(i, 1) != "" end)
end
defp mailbox({name, address}), do: "#{name} <#{address}>"
defp compile_parts(email) do
[
{:plain, email.text_body},
{:html, email.html_body},
Enum.map(email.attachments, fn attachment ->
{:attachment, attachment.data, attachment}
end)
]
|> List.flatten()
|> Enum.filter(¬_empty_tuple_value(&1))
end
defp not_empty_tuple_value(tuple) when is_tuple(tuple) do
value = elem(tuple, 1)
value != nil && value != [] && value != ""
end
end