elixir-mail icon indicating copy to clipboard operation
elixir-mail copied to clipboard

Extract address from "to", "from", "cc", etc.

Open anthonator opened this issue 5 years ago • 2 comments

I've run into an issue when using gen_smtp where providing an address that's a tuple or contains a name causes the SMTP server (AWS SES in this case) to return an invalid email error. The fix is to extract the address from the tuple/string with name. I'm curious if introducing something like get_address(message, header) makes sense for this project. I'd expect this to be a common problem for folks.

Here's an example of the problem I'm referring to.

message =
  Mail.build()
  |> Mail.put_from("Me <[email protected]>")
  |> Mail.put_to("You <[email protected]>")
  |> Mail.put_subject("Hello, you!")
  |> Mail.put_text("Sup")

from = Mail.get_from(message)  # returns "Me <[email protected]>"
to = Mail.get_to(message) # returns "You <[email protected]>"
body = Mail.render(message)

:gen_smtp_client({ from, to, body }, config) # returns a 533 invalid address 😵

You can fix the the problem by making both from and to plain addresses instead of the name/address combo. However, the From and To headers now won't contain the name. This puts the burden on the users of mail to figure this out.

My proposal is to provide a get_address/2 function that would provide this functionality.

message =
  Mail.build()
  |> Mail.put_from("Me <[email protected]>")
  |> Mail.put_to("You <[email protected]>")
  |> Mail.put_subject("Hello, you!")
  |> Mail.put_text("Sup")

from = Mail.get_address(message, :from) # returns "[email protected]"
to = Mail.get_address(message, :to) # returns "[email protected]"
body = Mail.render(message)

:gen_smtp_client({ from, to, body }, config) # it works 🎉

We've implemented something similar in our project and I'd be happy to provide a PR.

anthonator avatar Feb 05 '20 22:02 anthonator

Here's what I've put together to solve this problem for us.

defp address([h | t]) do
  [address(h)] ++ address(t)
end

defp address([]) do
  []
end

defp address({ _name, address }) do
  address
end

defp address(string) when is_binary(string) do
  case Regex.run(~r/.+ <(.+)>/, string) do
    nil ->
      string
    [_, address] ->
      address
  end
end

anthonator avatar Feb 05 '20 22:02 anthonator

Digging deeper I found Mail.Parsers.RFC2822.parse_recipient_value/1. We may be able to use this to provide a function that just returns an address.

https://github.com/DockYard/elixir-mail/blob/master/lib/mail/parsers/rfc_2822.ex#L178-L184

anthonator avatar Feb 06 '20 12:02 anthonator