who_need_help/lib/who_need_help/accounts/user_notifier.ex

206 lines
7.7 KiB
Elixir

defmodule WhoNeedHelp.Accounts.UserNotifier do
use Gettext, backend: WhoNeedHelpWeb.Gettext
import Swoosh.Email
alias WhoNeedHelp.Accounts.User
alias WhoNeedHelp.EmailDelivery
# Delivers the email using the application mailer.
defp deliver(recipient, subject, text_body, html_body, kind) do
from = Application.fetch_env!(:who_need_help, :mailer_from)
email =
new()
|> to(recipient)
|> from({from[:name], from[:address]})
|> subject(subject)
|> text_body(text_body)
|> html_body(html_body)
with {:ok, _metadata} <- EmailDelivery.deliver(email, kind) do
{:ok, email}
end
end
@doc """
Deliver instructions to update a user email.
"""
def deliver_update_email_instructions(user, url) do
with_user_locale(user, fn ->
deliver_action_email(user,
kind: :auth_email_change,
subject: gettext("Confirm your Who Need Help email change"),
heading: gettext("Confirm your new email address"),
introduction:
gettext("Use the secure link below to confirm this email address for your account."),
action_label: gettext("Confirm email address"),
url: url,
expiry_note: gettext("This confirmation link expires in 7 days."),
security_note:
gettext(
"If you did not request this change, ignore this email. Your address will not change."
)
)
end)
end
@doc """
Deliver instructions to log in with a magic link.
"""
def deliver_login_instructions(user, url) do
case user do
%User{confirmed_at: nil} -> deliver_confirmation_instructions(user, url)
_ -> deliver_magic_link_instructions(user, url)
end
end
@doc "Delivers instructions for verifying a local account before connecting Google sign-in."
def deliver_google_link_instructions(user, url) do
with_user_locale(user, fn ->
deliver_action_email(user,
kind: :auth_google_link,
subject: gettext("Confirm Google sign-in for Who Need Help"),
heading: gettext("Confirm Google sign-in"),
introduction:
gettext("Use the secure link below to sign in and connect Google to your account."),
action_label: gettext("Confirm Google sign-in"),
url: url,
expiry_note: gettext("This one-time link expires in 15 minutes."),
security_note:
gettext("If you did not request this, ignore this email. Google will not be connected.")
)
end)
end
defp deliver_magic_link_instructions(user, url) do
with_user_locale(user, fn ->
deliver_action_email(user,
kind: :auth_login,
subject: gettext("Your Who Need Help sign-in link"),
heading: gettext("Sign in to Who Need Help"),
introduction: gettext("Use the secure link below to sign in to your account."),
action_label: gettext("Sign in to Who Need Help"),
url: url,
expiry_note: gettext("This one-time link expires in 15 minutes."),
security_note:
gettext("If you did not request this sign-in, you can safely ignore this email.")
)
end)
end
defp deliver_confirmation_instructions(user, url) do
with_user_locale(user, fn ->
deliver_action_email(user,
kind: :auth_registration,
subject: gettext("Confirm your Who Need Help account"),
heading: gettext("Confirm your account"),
introduction: gettext("Use the secure link below to confirm your Who Need Help account."),
action_label: gettext("Confirm account"),
url: url,
expiry_note: gettext("This one-time link expires in 15 minutes."),
security_note:
gettext("If you did not create this account, you can safely ignore this email.")
)
end)
end
defp deliver_action_email(user, content) do
kind = Keyword.fetch!(content, :kind)
subject = Keyword.fetch!(content, :subject)
heading = Keyword.fetch!(content, :heading)
introduction = Keyword.fetch!(content, :introduction)
action_label = Keyword.fetch!(content, :action_label)
url = Keyword.fetch!(content, :url)
expiry_note = Keyword.fetch!(content, :expiry_note)
security_note = Keyword.fetch!(content, :security_note)
text = """
#{heading}
#{introduction}
#{action_label}: #{url}
#{expiry_note}
#{security_note}
Who Need Help
"""
html = action_email_html(heading, introduction, action_label, url, expiry_note, security_note)
deliver(user.email, subject, text, html, kind)
end
defp action_email_html(heading, introduction, action_label, url, expiry_note, security_note) do
escaped_heading = escape_html(heading)
escaped_introduction = escape_html(introduction)
escaped_action_label = escape_html(action_label)
escaped_url = escape_html(url)
escaped_expiry_note = escape_html(expiry_note)
escaped_security_note = escape_html(security_note)
"""
<!doctype html>
<html lang="#{escape_html(Gettext.get_locale(WhoNeedHelpWeb.Gettext))}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light">
<title>#{escaped_heading}</title>
</head>
<body style="margin:0;padding:0;background:#f5f7f6;color:#1d1d20;font-family:Arial,Helvetica,sans-serif;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#f5f7f6;">
<tr>
<td align="center" style="padding:32px 16px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="max-width:560px;background:#ffffff;border:1px solid #dfe5e2;border-radius:16px;">
<tr>
<td style="padding:28px 32px 12px;font-size:18px;font-weight:700;color:#007d6b;">Who Need Help</td>
</tr>
<tr>
<td style="padding:8px 32px 0;">
<h1 style="margin:0;font-size:26px;line-height:1.25;color:#1d1d20;">#{escaped_heading}</h1>
</td>
</tr>
<tr>
<td style="padding:16px 32px 0;font-size:16px;line-height:1.6;color:#555b58;">#{escaped_introduction}</td>
</tr>
<tr>
<td style="padding:24px 32px;">
<a href="#{escaped_url}" style="display:inline-block;padding:13px 20px;border-radius:8px;background:#a93612;color:#ffffff;text-decoration:none;font-size:16px;font-weight:700;">#{escaped_action_label}</a>
</td>
</tr>
<tr>
<td style="padding:0 32px 8px;font-size:14px;line-height:1.55;color:#555b58;">#{escaped_expiry_note}</td>
</tr>
<tr>
<td style="padding:8px 32px 0;font-size:14px;line-height:1.55;color:#555b58;">#{escaped_security_note}</td>
</tr>
<tr>
<td style="padding:20px 32px 8px;font-size:12px;line-height:1.5;color:#747a77;">#{escape_html(gettext("If the button does not work, copy and paste this address into your browser:"))}</td>
</tr>
<tr>
<td style="padding:0 32px 28px;font-size:12px;line-height:1.5;word-break:break-all;"><a href="#{escaped_url}" style="color:#007d6b;">#{escaped_url}</a></td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
"""
end
defp escape_html(value) do
value
|> Phoenix.HTML.html_escape()
|> Phoenix.HTML.safe_to_string()
end
defp with_user_locale(%User{locale: locale}, fun) do
Gettext.with_locale(WhoNeedHelpWeb.Gettext, WhoNeedHelp.Locales.normalize(locale), fun)
end
end