Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate correct MIME payload #59

Merged
merged 5 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import Config

if Mix.env() == :test do
config :logger, level: :info
end

# import_config "#{Mix.env}.exs"
18 changes: 14 additions & 4 deletions lib/bamboo/adapters/message/content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ defmodule BambooSes.Message.Content do
Depending on email it can generate simple, raw or template content.
"""

alias BambooSes.Encoding

@type t :: %__MODULE__{
Template:
%{
Expand Down Expand Up @@ -78,8 +80,8 @@ defmodule BambooSes.Message.Content do
when is_map(template_params),
do: %__MODULE__{Template: template_params}

defp build_content(_email, _template_params, subject, text, html, [], []),
do: build_simple_content(subject, text, html)
defp build_content(_email, _template_params, subject, text, html, headers, []),
do: build_simple_content(subject, text, html, headers)

defp build_content(email, _template_params, _subject, _text, _html, _headers, _attachments) do
raw_data =
Expand All @@ -94,18 +96,26 @@ defmodule BambooSes.Message.Content do
}
end

defp build_simple_content(subject, text, html) do
defp build_simple_content(subject, text, html, headers) do
%__MODULE__{
Simple: %{
Subject: %{
Charset: "UTF-8",
Data: subject
},
Body: build_simple_body(text, html)
Body: build_simple_body(text, html),
Headers: build_headers(headers)
}
}
end

defp build_headers(headers) do
Enum.map(
headers,
fn {name, value} -> %{"Name" => name, "Value" => Encoding.maybe_rfc1342_encode(value)} end
)
end

defp build_simple_body(text, html) do
%{}
|> put_text(text)
Expand Down
23 changes: 0 additions & 23 deletions lib/bamboo/adapters/render/LICENSE.txt

This file was deleted.

247 changes: 166 additions & 81 deletions lib/bamboo/adapters/render/raw.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,79 +10,181 @@ defmodule BambooSes.Render.Raw do
alias BambooSes.Encoding

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)
has_text = !is_nil(email.text_body) && String.length(email.text_body) > 0
has_html = !is_nil(email.html_body) && String.length(email.html_body) > 0
has_attachments = length(filter_regular_attachments(email)) > 0
has_inline_attachments = length(filter_inline_attachments(email)) > 0

headers = headers_for(email) ++ extra_headers

build_parts(
has_text,
has_html,
has_attachments,
has_inline_attachments,
email,
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)
defp build_parts(false, false, _, _, email, headers) do
{
"multipart",
"mixed",
headers,
%{},
prepare_attachments(email.attachments)
}
end

defp build_parts(false, true, false, false, email, headers) do
{
"text",
"html",
headers,
parameters_for(nil),
email.html_body
}
end

defp build_parts(false, true, false, true, email, headers) do
{
top_mime_type,
top_mime_sub_type,
headers_for(email) ++ extra_headers,
"multipart",
"related",
headers,
%{},
top_content_part
[
# generates html
build_parts(false, true, false, false, email, [])
] ++ prepare_attachments(filter_inline_attachments(email))
}
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
defp build_parts(false, true, true, false, email, headers) do
{
"multipart",
"mixed",
headers,
%{},
[
# generates html
build_parts(false, true, false, false, email, [])
] ++ prepare_attachments(email.attachments)
}
end

defp build_parts(false, true, true, true, email, headers) do
{
"multipart",
"mixed",
headers,
%{},
[
# generates html
build_parts(false, true, false, true, email, [])
] ++ prepare_attachments(filter_regular_attachments(email))
}
end

defp build_parts(true, false, false, false, email, headers) do
{
"text",
"plain",
headers,
parameters_for(nil),
email.text_body
}
end

defp build_parts(true, false, _, _, email, headers) do
{
"multipart",
"mixed",
headers,
%{},
[
# generates text
build_parts(true, false, false, false, email, [])
] ++ prepare_attachments(email.attachments)
}
end

defp build_parts(true, true, false, false, email, headers) do
{
"multipart",
"alternative",
headers,
%{},
[
# generates text
build_parts(true, false, false, false, email, []),
# generates html
build_parts(false, true, false, false, email, [])
]
}
end

defp build_parts(true, true, false, true, email, headers) do
{
"multipart",
"related",
headers,
%{},
[
# generates alternative
build_parts(true, true, false, false, email, [])
] ++ prepare_attachments(filter_inline_attachments(email))
}
end

defp build_parts(true, true, true, false, email, headers) do
{
"multipart",
"mixed",
headers,
%{},
[
# generates alternative
build_parts(true, true, false, false, email, [])
] ++ prepare_attachments(email.attachments)
}
end

defp build_parts(true, true, true, true, email, headers) do
{
"multipart",
"mixed",
headers,
%{},
[
# generates related with alternative
build_parts(true, true, false, true, email, [])
] ++ prepare_attachments(filter_regular_attachments(email))
}
end

defp prepare_attachments(attachments) do
attachments
|> Enum.map(fn attachment -> {:attachment, attachment.data, attachment} end)
|> attachment_part_tuples()
end

def filter_inline_attachments(email) do
Enum.filter(email.attachments, fn
attachment ->
!is_nil(attachment) &&
!is_nil(attachment.content_id) &&
String.length(attachment.content_id) > 0
end)
end

def filter_regular_attachments(email) do
Enum.filter(email.attachments, fn
attachment ->
!is_nil(attachment) &&
(is_nil(attachment.content_id) || String.length(attachment.content_id) == 0)
end)
end

@spec attachment_part_tuples([tuple()]) :: list(tuple())
Expand Down Expand Up @@ -178,21 +280,4 @@ defmodule BambooSes.Render.Raw do
end

defp preprocess_header({key, value}), do: {key, value}

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(&not_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
8 changes: 4 additions & 4 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
%{
"bamboo": {:hex, :bamboo, "2.2.0", "f10a406d2b7f5123eb1f02edfa043c259db04b47ab956041f279eaac776ef5ce", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8c3b14ba7d2f40cb4be04128ed1e2aff06d91d9413d38bafb4afccffa3ade4fc"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"},
"credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"},
"dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
"eiconv": {:hex, :eiconv, "1.0.0", "ee1e47ee37799a05beff7a68d61f63cccc93101833c4fb94b454c23b12a21629", [:rebar3], [], "hexpm", "8c80851decf72fc4571a70278d7932e9a87437770322077ecf797533fbb792cd"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_aws": {:hex, :ex_aws, "2.5.3", "9c2d05ba0c057395b12c7b5ca6267d14cdaec1d8e65bdf6481fe1fd245accfb4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67115f1d399d7ec4d191812ee565c6106cb4b1bbf19a9d4db06f265fd87da97e"},
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
Expand Down
Loading
Loading