-
Notifications
You must be signed in to change notification settings - Fork 229
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
Inline attachments #159
Inline attachments #159
Conversation
…dicate new addition
Hi Theo, I'm on my phone. I've scanned through the PR, it's mostly good. I'll go through it again on my desktop soon. Probably just nits so I might simply merge this and tweak the styles / implementation details myself. Po |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @theodowling
This is my first round review on the PR.
The comments are for both myself and @stevedomin to think about. You are welcome to address the nitpicks if you'd like to. If not, I'll clean it up before I merge.
@stevedomin the main things I want to talk about are type: :inline|:attachment
vs inline: true|false
, and whether to special case inline attachments and perform checks on content types, etc.
@theodowling out of curiosity, have you looked into SMTP inline attachments?
lib/swoosh/adapters/mailgun.ex
Outdated
@@ -86,10 +88,23 @@ defmodule Swoosh.Adapters.Mailgun do | |||
|
|||
defp prepare_attachments(body, %{attachments: []}), do: body | |||
defp prepare_attachments(body, %{attachments: attachments}) do | |||
Map.put(body, :attachments, Enum.map(attachments, &prepare_file(&1))) | |||
normal_attachments = Enum.filter(attachments, fn(%Swoosh.Attachment{type: type}) -> type == :attachment end) | |||
inline_attachments = Enum.filter(attachments, fn(%Swoosh.Attachment{type: type}) -> type == :inline end) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enum.split_with
- Matching struct type in private functions is not necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Thanks for recommending Enum.split_with
. I'll fix this on my side.
lib/swoosh/adapters/mailgun.ex
Outdated
{:multipart, | ||
params | ||
|> Map.drop([:attachments]) | ||
|> Map.drop([:inline]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Map.drop([:attachments, :inline])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I'll update this.
lib/swoosh/adapters/mandrill.ex
Outdated
|
||
inline_attachments = Enum.map(inline_attachments, fn %Swoosh.Attachment{content_type: type, path: path, filename: filename} -> | ||
content = path |> File.read! |> Base.encode64 | ||
%{type: type, name: "#{filename}", content: content} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I'd extract this common part into a separate function
"#{filename}"
is justfilename
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has also been updated. Thanks.
@@ -3,7 +3,7 @@ defmodule Swoosh.Attachment do | |||
Struct representing an attachment in an email. | |||
""" | |||
|
|||
defstruct filename: nil, content_type: nil, path: nil | |||
defstruct filename: nil, content_type: nil, path: nil, type: nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Maybe just
inline: false
(default to attachment)? - Should we do something special about inline attachments? e.g. validating it's an image?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with using type
. Slightly more future-proof.
@princemaple: regarding validation of inline attachments, do you know if the spec says anything about accepted file type for inline attachments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually failed to search for the inline attachment related specs. :(
lib/swoosh/email.ex
Outdated
@@ -464,23 +464,30 @@ defmodule Swoosh.Email do | |||
as an argument. If you give a path we will detect the MIME type and determine the filename | |||
automatically. | |||
|
|||
You can also send an inline-attachment used for embedding images in the body of emails by specifying `type: "inline"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently it's :inline
not "inline"
(might change, see comment above)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Thanks @princemaple - I'll wait for you and @stevedomin to discuss the handling of I'll also commit some small fixes/changes on the back of your comments above. I haven't looked at the SMTP implementation, but I can do that also today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@theodowling thank you so much for this, great work!
Pointed out a few things in comment but otherwise this is good to go.
@@ -3,7 +3,7 @@ defmodule Swoosh.Attachment do | |||
Struct representing an attachment in an email. | |||
""" | |||
|
|||
defstruct filename: nil, content_type: nil, path: nil | |||
defstruct filename: nil, content_type: nil, path: nil, type: nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with using type
. Slightly more future-proof.
@princemaple: regarding validation of inline attachments, do you know if the spec says anything about accepted file type for inline attachments?
mix.lock
Outdated
@@ -1,20 +1,20 @@ | |||
%{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New version of Hex?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More like an older version. @theodowling Please exclude the mix.lock change :) You might want to upgrade with mix local.hex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the heads-up. I've reverted change to mix.lock
lib/swoosh/email.ex
Outdated
@@ -464,23 +464,30 @@ defmodule Swoosh.Email do | |||
as an argument. If you give a path we will detect the MIME type and determine the filename | |||
automatically. | |||
|
|||
You can also send an inline-attachment used for embedding images in the body of emails by specifying `type: "inline"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Thanks for the Approval. |
@theodowling thanks for looking into it! |
@theodowling nice work on the SMTP attachment! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @theodowling
Thanks a lot for this PR.
This is my final round of review. It's 100% nitpicks.
Feel free to ignore them if you can't be bothered :) , I'll fix them before/after merge.
@stevedomin a final 👍 ?
lib/swoosh/adapters/mailgun.ex
Outdated
@@ -86,13 +88,17 @@ defmodule Swoosh.Adapters.Mailgun do | |||
|
|||
defp prepare_attachments(body, %{attachments: []}), do: body | |||
defp prepare_attachments(body, %{attachments: attachments}) do | |||
Map.put(body, :attachments, Enum.map(attachments, &prepare_file(&1))) | |||
{normal_attachments, inline_attachments} = Enum.split_with(attachments, fn(%Swoosh.Attachment{type: type}) -> type == :attachment end) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
separate lines
{..} =
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove Swoosh.Attachment
and unnecessary braces, fn %{...} -> ... end is sufficient
lib/swoosh/adapters/mandrill.ex
Outdated
"content" => &1.path |> File.read! |> Base.encode64 | ||
})) | ||
normal_attachments = prepare_attachments_structure(Enum.filter(attachments, fn(attachment) -> attachment.type == :attachment end)) | ||
inline_attachments = prepare_attachments_structure(Enum.filter(attachments, fn(attachment) -> attachment.type == :inline end)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enum.split_with
(might as well just copy paste the change from mailgun)
lib/swoosh/adapters/mandrill.ex
Outdated
end | ||
|
||
defp prepare_attachments_structure(attachments) do | ||
Enum.map(attachments, fn %Swoosh.Attachment{content_type: type, path: path, filename: filename} -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove Swoosh.Attachment
lib/swoosh/adapters/postmark.ex
Outdated
"ContentType" => &1.content_type, | ||
"Content" => &1.path |> File.read! |> Base.encode64 | ||
})) | ||
Map.put(body, "Attachments", Enum.map(attachments, fn(attachment) -> prepare_attachment(attachment) end)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unnecessary braces in fn arguments
lib/swoosh/adapters/postmark.ex
Outdated
defp prepare_attachment(attachment) do | ||
case attachment.type do | ||
:inline -> %{"Name" => attachment.filename, | ||
"ContentType" => attachment.content_type, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indentation is off
lib/swoosh/adapters/sendgrid.ex
Outdated
@@ -106,9 +106,12 @@ defmodule Swoosh.Adapters.Sendgrid do | |||
|
|||
defp prepare_attachments(body, %{attachments: []}), do: body | |||
defp prepare_attachments(body, %{attachments: attachments}) do | |||
attachments = Enum.map(attachments, fn %{content_type: type, path: path, filename: filename} -> | |||
attachments = Enum.map(attachments, fn %Swoosh.Attachment{content_type: content_type, path: path, filename: filename, type: type} -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove Swoosh.Attachment
lib/swoosh/adapters/sparkpost.ex
Outdated
@@ -57,6 +57,8 @@ defmodule Swoosh.Adapters.SparkPost do | |||
html_body: html, | |||
attachments: attachments | |||
} = email) do | |||
normal_attachments = Enum.filter(attachments, fn(attachment) -> attachment.type == :attachment end) | |||
inline_attachments = Enum.filter(attachments, fn(attachment) -> attachment.type == :inline end) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
split_with
Hi @princemaple, thanks for the comments. I've made the changes as requested. |
I'm 👍, feel free to merge if you feel it's good to go @princemaple |
@theodowling thank you very much for the great work. Merging now! |
You would do well to update your documentation to mention the bit about the cid name being the filename for inline attachments. Even better to provide a simple example. (searching github bugs is a poor replacement for docs) |
@blackham It's not very helpful to comment on a PR closed over 3 years ago. It'd be better you filed an issue to tell us what troubled you, and how we can improve. Basically what you did with the comment. Even better you could send a PR to address the issue. |
Sorry it was late. Let me try and explain and give you example code you might be able to use in the doc. Also sorry for commenting on an old bug. It would have saved me some time last night if the text "Inline attachments by default use their filename (or basename of the path if filename is not specified) as cid, in relevant adapters." found in https://hexdocs.pm/swoosh/Swoosh.Attachment.html also gave a very basic example. Just this would have been enough for the light bulb to go off. Or better, I could have skipped most everything else in the docs and just looked at this example:
Maybe OTP (One Time Password) isn't the best in that example. Using "OTP" for anything but Open Telecom Platform in Elixir might be against some peoples religion. But TwoFA.qrcode_png(user) is ugly. TwoFactorAuth.qrcode_png(user) or TFA.qrcode_png(user)? In any case seeing the BTW, Great job on a mail system! |
Thanks for that. I'll update the docs to include your example. Documentation PRs are always welcome. |
Pushed more docs updates. |
Hi there
I needed inline-attachment support for a project, so I added it for myself, and thought it might make a good PR.
Inline-Attachment support has been added to Sendgrid, Mandrill, Mailgun, Postmark and SparkPost adapters.
Update: SMTP adapter also supported
At its core you just need to specify
type: :inline
when creating an Attachment, and all the rest will happen automatically.And the ContentID of the attachment will default to filename so in the above case, you can refer to the inline-attached image by using:
I've tested using all of the adapters listed above. Please let me know if you have any comments, or need me to change anything.
Kind regards
Theo