-
Notifications
You must be signed in to change notification settings - Fork 31
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
Phoenix 1.7 and phasing out Phoenix.View #287
Comments
I haven't got around to test the changes yet, so no idea. PRs are welcome, if people think there is necessary and/or beneficial change. |
I am upgrading my small app to 1.7.0-rc.2 at the moment. I'm dropping Instead of defmodule AppWeb.EmailHTML do
use AppWeb, :html
embed_templates "../templates/email/*"
end I added a couple helper methods to my email module to render the heex templates into an HTML string that I could pass to Swoosh. defmodule AppWeb.Emails.UserEmail do
import Swoosh.Email
defp render_with_layout(email, heex) do
html_body(
email,
render_component(AppWeb.EmailHTML.layout(%{email: email, inner_content: heex}))
)
end
defp render_component(heex) do
heex |> Phoenix.HTML.Safe.to_iodata() |> IO.chardata_to_string()
end
def send_my_email(foo) do
new()
|> subject("Here's my email")
|> render_with_layout(AppWeb.EmailHTML.my_email(%{foo: foo}))
end
end That's all it took, and it seems to be working well! I like the simplicity of having everything in components. If Swoosh provided some convenience helpers for the rendering piece, that would be pretty handy. |
@iangreenleaf Yeah I've been wondering, after seeing the embed_templates/2, how much value Thanks a lot for sharing! |
I'd say more, but at the very least it can act as a compatibility layer. For large and complex email communication cases that's already a lot of value |
Thanks. This can be accomplished by pinning an existing version. I was more wondering if I was to release a 2.0 of this package that is designed with phoenix 1.7 as a starting point, what more this package can provide to the users. 🤔 |
I understand. I meant the 1.7+ and getting away with "Views" as this is probably what will have to happen anyway. Something that would keep the existing codebase work after migrating to a Phoenix version, which no longer has them in their current form. Well, probably with only minor modifications at the top of each related file. Whether you find that worth the effort that's of course a different story. Also, I am not sure if there's good enough support for multiple languages. I'd be happy to see something that simplifies picking a correct language variant of the template as well as an ability to build the final body from several templates. Can be for a multilingual message for example or for building larger messages from smaller building blocks |
With
This is probably the only value left. |
@iangreenleaf thx for the code example, if somebody has the
defp render_with_layout(email, heex) do
html_body(
email,
render_component(AppWeb.Layouts.email(%{email: email, inner_content: heex}))
)
end |
@preciz this can be different for every repo depending on the setup and when the project was generated. |
You are right, I intended to bring it closer to somebody who is looking at a newly generated 1.7 Phoenix codebase, I edit my comment. |
Hi! Thanks for opening this issue, very helpful to read during a Phoenix 1.7 upgrade for a project using In reply to this part of the conversation:
I'd like to add that The new embed_templates/2 function does support multiple formats, but a This leaves a bit more work to do compared to the automatic dual HTML/text format detection |
Also something to consider for a new view-less version: a fresh start under the name of |
That is true. I sometimes think that too. (I didn't create this project) |
I also updated a small app to 1.7 wanting to drop the def generate_email("update_email", user, %{"url" => url}) do
user
|> base_email(dgettext("emails", "Update your email"))
|> render_body(:update_email, %{user: user, url: url})
end
defp render_body(email, template, assigns) do
html_heex = apply(EmailHTML, String.to_existing_atom("#{template}_html"), [assigns])
html = render_to_string(Layouts, "email_html", "html", email: email, inner_content: html_heex)
text_heex = apply(EmailHTML, String.to_existing_atom("#{template}_text"), [assigns])
text = render_to_string(Layouts, "email_text", "text", email: email, inner_content: text_heex)
email |> html_body(html) |> text_body(text)
end And then my defmodule MyAppWeb.EmailHTML do
use MyAppWeb, :html
embed_templates "email_html/*.html", suffix: "_html"
embed_templates "email_html/*.txt", suffix: "_text"
end Edit: Updated from original approach because it was a good idea, and I even checked that it worked 😄 |
This thread will eventually shape the next version of the library 🙂 |
To add # email.ex
defmodule Email do
import Phoenix.Template, only: [embed_templates: 2]
embed_templates("templates/email/*.mjml", suffix: "_mjml")
embed_templates("templates/email/*.text", suffix: "_text")
end
# user_notifier.ex
defmodule UserNotifier do
import Swoosh.Email
def welcome(user) do
assigns = %{name: user.name}
new()
|> html_body_with_layout(Email.welcome_mjml(assigns))
|> text_body_with_layout(Email.welcome_text(assigns))
end
defp html_body_with_layout(email, inner_content) do
body =
%{email: email, inner_content: inner_content}
|> Email.layout_mjml()
|> to_binary()
|> to_html()
html_body(email, body)
end
defp text_body_with_layout(email, inner_content) do
body =
%{email: email, inner_content: inner_content}
|> Email.layout_text()
|> to_binary()
text_body(email, body)
end
defp to_binary(rendered), do: rendered |> Phoenix.HTML.Safe.to_iodata() |> IO.iodata_to_binary()
defp to_html(mjml_binary), do: with({:ok, html} <- Mjml.to_html(mjml_binary), do: html)
end |
@ftes that's pretty comprehensive! Cheers |
Very good discussion folks. Just one quick addition, you can use Phoenix.Template.render_to_string in some of the cases above so you encapsulate the Safe.to_iodata conversion. :) |
Here is another tip, you don't even need to define a module with templates. From Phoenix v1.7, you can use ~H directly, especially if you are not using layouts: defmodule Hub.Notifier do
use HubWeb, :html
require Logger
def deliver_invitation(org, inviter, email) do
assigns = %{org: org, inviter: inviter, email: email}
email_body = ~H"""
<p>Hi <%= @email %>,</p>
...
"""
deliver(email, "You're invited to join #{org.name}", email_body)
end
defp deliver(recipient, subject, body) do
email =
Swoosh.Email.new(
to: recipient,
from: {"Livebook Teams", "[email protected]"},
subject: subject,
html_body:
body
|> Phoenix.HTML.html_escape()
|> Phoenix.HTML.safe_to_string()
)
case Mailer.deliver(email) do
{:ok, _} ->
{:ok, email}
{:error, reason} ->
Logger.warning("Sending email failed: #{inspect(reason)}")
{:error, reason}
end
end
end So I think there are strong arguments that perhaps this lib is no longer necessary indeed. :) |
Cheers @josevalim. Maybe Phoenix can have an official comprehensive Notifier guide, then we can formally retire this lib :) |
Good idea. If anyone would like to contribute one, feel free to PR one and ping me, I will be glad to review and guide its way in! |
Here is an interesting article to keep the discussion going: https://andrewian.dev/blog/phoenix-email-defaults |
In addition to a dedicated notifier guide, it might be an idea to enhance the |
Something like this seems to do the trick. defmodule PhxMail.Notifier do
use PhxMail, :html
import Swoosh.Email
require EEx
def welcome(assigns) do
mjml = """
<mjml>
<mj-body>
<mj-section padding-top="30px" padding-bottom="30px">
<mj-column>
<mj-text>Hello {{first_name}},</mj-text>
<mj-text>Welcome!</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
"""
{:ok, html} = Mjml.to_html(mjml)
template = html_to_heex(html)
final_email = EEx.eval_string(template, assigns: assigns)
new()
|> to({assigns.first_name <> " " <> assigns.last_name, assigns.email})
|> from({"ABC", "[email protected]"})
|> subject("Welcome!")
|> html_body(final_email)
end
defp html_to_heex(html) do
~r/{{\s*([^}^\s]+)\s*}}/
|> Regex.replace(html, fn _, variable_name ->
"<%= @#{variable_name} %>"
end)
end
end |
defmodule MyApp.Notifier do
import Swoosh.Email
require EEx
def welcome(assigns) do
mjml = """
<mjml>
<mj-body>
<mj-section padding-top="30px" padding-bottom="30px">
<mj-column>
<mj-text>Hello <%= @first_name %>,</mj-text>
<mj-text>Welcome!</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
"""
{:ok, html} = EEx.eval_string(mjml, assigns: assigns) |> Mjml.to_html()
new()
|> to({assigns.first_name <> " " <> assigns.last_name, assigns.email})
|> from({"ABC", "[email protected]"})
|> subject("Welcome!")
|> html_body(html)
end
end @aoioioi you can use EEx directly without the Regex conversion and i don't think that |
👋 While working on auto-generating text emails from HTML emails (apparently a text part is sometimes required by some relays) I remembered this issue and wondered, why not use Markdown for both? If there was a library like begriffs/mimedown in Elixir, then maybe it would've been possible to do something like this: defmodule MyApp.Notifier do
import Swoosh.Email
def welcome(assigns) do
%{html: html, text: text} = MyApp.Mimedown.render("welcome.md", assigns)
new()
|> to({assigns.first_name <> " " <> assigns.last_name, assigns.email})
|> from({"ABC", "[email protected]"})
|> subject("Welcome!")
|> html_body(html)
|> text_body(text)
end
end And editing Markdown templates would have been super easy with VSCode previews. Thoughts? |
Yeah, big fan of this approach. I introduced a similar style back in Rails a long long long time ago. :) |
For new projects generated with Phoenix 1.7
Phoenix.View
has been replaced withPhoenix.Component
. Are there any plans to updatePhoenix.Swoosh
to reflect this?The text was updated successfully, but these errors were encountered: