Packages
phoenix_kit
1.7.208
1.7.211
1.7.210
1.7.209
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_web/live/settings/email_sending.ex
defmodule PhoenixKitWeb.Live.Settings.EmailSending do
@moduledoc """
Core "Email Sending" admin settings page (`/admin/settings/email-sending`).
Covers what core owns about outbound email: sender identity, which
transport is actually in effect (static app-config mailer vs. a
connected Integrations provider), the operator's choice of default send
integration, and a test-send action. Send Profiles (per-account sender
identity, rate limits, provider-specific options) live one level down
at `/admin/settings/email-sending/profiles`.
## Path note
This page is deliberately at `email-sending`, not `emails` — the
optional `phoenix_kit_emails` module registers its own routable
"Emails" settings tab at `/admin/settings/emails` (via its
`settings_tabs/0`). The two pages coexist under different paths for
now; a later change (Stage 1 task A5) will collapse them into one.
## Module-contributed sections
Modules can extend this page without the core page knowing anything
about them, via `PhoenixKit.Module.email_settings_sections/0` — see
`PhoenixKit.ModuleRegistry.all_email_settings_sections/0`. Each
section is a module-owned `Phoenix.LiveComponent`, rendered below the
core sections, gated by its declared permission (or shown to any admin
when the permission is `nil`).
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Config
alias PhoenixKit.Integrations
alias PhoenixKit.Integrations.Providers
alias PhoenixKit.Mailer
alias PhoenixKit.ModuleRegistry
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth.Scope
alias PhoenixKit.Utils.Routes
@default_integration_setting "default_email_integration_uuid"
def mount(_params, _session, socket) do
socket =
socket
|> assign(:page_title, gettext("Email Sending"))
|> assign(
:page_subtitle,
gettext(
"Sender identity, transport, and the default integration used to deliver outbound email"
)
)
|> assign(:project_title, Settings.get_project_title())
|> assign(:current_path, get_current_path(socket.assigns.current_locale_base))
|> assign_sender_identity()
|> assign_transport_info()
|> assign_email_integrations()
|> assign_default_integration()
|> assign_email_settings_sections()
{:ok, socket}
end
def handle_params(_params, _url, socket) do
{:noreply, socket}
end
# ---------------------------------------------------------------------------
# Events
# ---------------------------------------------------------------------------
def handle_event("save_sender_identity", %{"from_name" => name, "from_email" => email}, socket) do
name = String.trim(name)
email = String.trim(email)
with {:ok, _} <- Settings.update_setting("from_name", name),
{:ok, _} <- Settings.update_setting("from_email", email) do
{:noreply,
socket
|> put_flash(:info, gettext("Sender identity updated"))
|> assign_sender_identity()}
else
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, gettext("Could not save sender identity"))}
end
end
def handle_event("select_default_integration", %{"integration_uuid" => uuid}, socket) do
case Settings.update_setting(@default_integration_setting, uuid) do
{:ok, _} ->
{:noreply,
socket
|> put_flash(:info, gettext("Default send integration updated"))
|> assign_default_integration()}
{:error, _changeset} ->
{:noreply,
put_flash(socket, :error, gettext("Could not update default send integration"))}
end
end
def handle_event("send_test_email", %{"recipient" => recipient}, socket) do
case String.trim(recipient) do
"" ->
{:noreply, put_flash(socket, :error, gettext("Enter a recipient email address"))}
recipient ->
send_test_email(socket, recipient)
end
end
defp send_test_email(socket, recipient) do
email =
Swoosh.Email.new()
|> Swoosh.Email.to(recipient)
|> Swoosh.Email.from({Mailer.get_from_name(), Mailer.get_from_email()})
|> Swoosh.Email.subject(
gettext("Test email from %{site}", site: socket.assigns.project_title)
)
|> Swoosh.Email.text_body(
gettext("This is a test email sent from the Email Sending settings page.")
)
case Mailer.deliver_email(email) do
{:ok, _result} ->
{:noreply,
put_flash(
socket,
:info,
gettext("Test email sent to %{recipient}", recipient: recipient)
)}
{:error, {:incomplete_credentials, missing_fields}} ->
{:noreply,
put_flash(
socket,
:error,
gettext(
"Could not send test email: the send integration is missing required field(s): %{fields}",
fields: Enum.map_join(missing_fields, ", ", &to_string/1)
)
)}
{:error, reason} ->
{:noreply,
put_flash(
socket,
:error,
gettext("Could not send test email: %{reason}", reason: inspect(reason))
)}
end
end
# ---------------------------------------------------------------------------
# Private — assigns
# ---------------------------------------------------------------------------
defp assign_sender_identity(socket) do
socket
|> assign(:from_name, Settings.get_setting("from_name", ""))
|> assign(:from_email, Settings.get_setting("from_email", ""))
|> assign(:effective_from_name, Mailer.get_from_name())
|> assign(:effective_from_email, Mailer.get_from_email())
end
defp assign_transport_info(socket) do
mailer = Mailer.get_mailer()
built_in? = mailer == PhoenixKit.Mailer
config =
if built_in?,
do: Config.get(mailer, []),
else: Config.get_parent_app_config(mailer, [])
socket
|> assign(:mailer_module, mailer)
|> assign(:mailer_built_in?, built_in?)
|> assign(:mailer_adapter, Keyword.get(config, :adapter))
end
# Single query for all email-capable providers' connections, mirroring
# `PhoenixKitWeb.Live.Settings.Integrations.load_connections/1`.
defp assign_email_integrations(socket) do
providers = Providers.with_capability(:email_send)
provider_keys = Enum.map(providers, & &1.key)
providers_by_key = Map.new(providers, &{&1.key, &1})
all_connections = Integrations.load_all_connections(provider_keys)
connections =
Enum.flat_map(providers, fn provider ->
all_connections
|> Map.get(provider.key, [])
|> Enum.map(fn %{uuid: uuid, name: name, data: data} ->
%{provider: providers_by_key[provider.key], uuid: uuid, name: name, data: data}
end)
end)
assign(socket, :email_connections, connections)
end
defp assign_default_integration(socket) do
assign(
socket,
:default_integration_uuid,
Settings.get_setting(@default_integration_setting, "")
)
end
defp assign_email_settings_sections(socket) do
scope = socket.assigns[:phoenix_kit_current_scope]
sections =
ModuleRegistry.all_email_settings_sections()
|> Enum.filter(§ion_visible?(&1, scope))
assign(socket, :email_settings_sections, sections)
end
defp section_visible?(%{permission: nil}, _scope), do: true
defp section_visible?(%{permission: permission}, scope),
do: Scope.has_module_access?(scope, permission)
# ---------------------------------------------------------------------------
# Private — template helpers
# ---------------------------------------------------------------------------
defp integration_status_badge("connected"), do: {"badge-success", gettext("Connected")}
defp integration_status_badge("configured"), do: {"badge-warning", gettext("Not tested")}
defp integration_status_badge("disconnected"), do: {"badge-ghost", gettext("Not connected")}
defp integration_status_badge("error"), do: {"badge-error", gettext("Error")}
defp integration_status_badge(_), do: {"badge-ghost", gettext("Not configured")}
defp get_current_path(locale) do
Routes.path("/admin/settings/email-sending", locale: locale)
end
end