diff --git a/lib/bamboo/adapters/rfc2822_with_bcc.ex b/lib/bamboo/adapters/rfc2822_with_bcc.ex index 7bd770c..84872fb 100644 --- a/lib/bamboo/adapters/rfc2822_with_bcc.ex +++ b/lib/bamboo/adapters/rfc2822_with_bcc.ex @@ -200,30 +200,75 @@ defmodule Bamboo.SesAdapter.RFC2822Renderer do |> Integer.to_string() |> String.pad_leading(2, "0") + # this function has been duplicated while from a PR in progress in `DockYard/elixir-mail` + defp put_parts(message, parts) do + put_in(message.parts, message.parts ++ parts) + end + + # this function has been duplicated while from a PR in progress in `DockYard/elixir-mail` + defp delete_all_parts(message) do + put_in(message.parts, []) + end + + # this function has been duplicated while from a PR in progress in `DockYard/elixir-mail` + defp is_inline_attachment?(message), + do: Enum.member?(List.wrap(Mail.Message.get_header(message, :content_disposition)), "inline") + + defp split_attachment_parts(message) do + Enum.reduce(message.parts, [[], [], []], fn part, [texts, mixed, inlines] -> + cond do + match_content_type?(part, ~r/text\/(plain|html)/) -> + [[part | texts], mixed, inlines] + is_inline_attachment?(part) -> + [texts, mixed, [part | inlines]] + true -> # a mixed part - most likely an attachment + [texts, [part | mixed], inlines] + end + end) + |> Enum.map(&Enum.reverse/1) # retain ordering + end + defp reorganize(%Mail.Message{multipart: true} = message) do content_type = Mail.Message.get_content_type(message) - if Mail.Message.has_attachment?(message) do - text_parts = - Enum.filter(message.parts, &match_content_type?(&1, ~r/text\/(plain|html)/)) - |> Enum.sort(&(&1 > &2)) + [text_parts, mixed, inlines] = split_attachment_parts(message) + has_inline = Enum.any?(inlines) + has_mixed_parts = Enum.any?(mixed) + has_text_parts = Enum.any?(text_parts) + if has_inline || has_mixed_parts do + # If any attaching, change content type to mixed content_type = List.replace_at(content_type, 0, "multipart/mixed") message = Mail.Message.put_content_type(message, content_type) - if Enum.any?(text_parts) do - message = Enum.reduce(text_parts, message, &Mail.Message.delete_part(&2, &1)) - - mixed_part = + if has_text_parts do + # If any text with attachments, wrap in new part + body_part = Mail.build_multipart() |> Mail.Message.put_content_type("multipart/alternative") + |> put_parts(text_parts) - mixed_part = Enum.reduce(text_parts, mixed_part, &Mail.Message.put_part(&2, &1)) - put_in(message.parts, List.insert_at(message.parts, 0, mixed_part)) + # If any inline attachments, wrap together with text + # in a "multipart/related" part + body_part = if has_inline do + Mail.build_multipart() + |> Mail.Message.put_content_type("multipart/related") + |> Mail.Message.put_part(body_part) + |> put_parts(inlines) + else + body_part + end + + message + |> delete_all_parts() + |> Mail.Message.put_part(body_part) + |> put_parts(mixed) else + # If not text sections, leave all parts as is message end else + # If only text, change content type to alternative content_type = List.replace_at(content_type, 0, "multipart/alternative") Mail.Message.put_content_type(message, content_type) end diff --git a/lib/bamboo/adapters/ses_adapter.ex b/lib/bamboo/adapters/ses_adapter.ex index fd4ecbb..45719f4 100644 --- a/lib/bamboo/adapters/ses_adapter.ex +++ b/lib/bamboo/adapters/ses_adapter.ex @@ -71,12 +71,10 @@ defmodule Bamboo.SesAdapter do attachments, message, fn attachment, message -> - headers = - if attachment.content_id do - [content_id: attachment.content_id] - else - [] - end + headers = Map.get(attachment, :headers) || [] + headers = if attachment.content_id, + do: [{:content_id, attachment.content_id} | headers], + else: headers opts = [headers: headers]