Packages
phoenix_kit
1.7.150
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/mailer.ex
defmodule PhoenixKit.Mailer do
@moduledoc """
Mailer module for PhoenixKit emails.
This module handles sending emails such as
confirmation emails, password reset emails, magic link emails, etc.
It can work in two modes:
1. **Built-in mode**: Uses PhoenixKit's own Swoosh mailer (default)
2. **Delegation mode**: Uses the parent application's mailer when configured
## Configuration
To use your application's mailer instead of PhoenixKit's built-in one:
config :phoenix_kit,
mailer: MyApp.Mailer
When delegation is configured, all emails will be sent through your application's
mailer, allowing you to use a single mailer configuration across your entire application.
"""
use Swoosh.Mailer, otp_app: :phoenix_kit
import Swoosh.Email
alias PhoenixKit.Email.Provider
alias PhoenixKit.Users.Auth.User
require Logger
@doc """
Gets the mailer module to use for sending emails.
Returns the configured parent application mailer if set,
otherwise returns the built-in PhoenixKit.Mailer.
## Examples
iex> PhoenixKit.Mailer.get_mailer()
MyApp.Mailer # if configured
iex> PhoenixKit.Mailer.get_mailer()
PhoenixKit.Mailer # default
"""
def get_mailer do
PhoenixKit.Config.get(:mailer, __MODULE__)
end
@doc """
Sends an email using a template from the database.
This is the main function for sending emails using PhoenixKit's template system.
It automatically:
- Loads the template by name
- Renders it with provided variables
- Tracks template usage
- Sends the email with tracking
- Logs to EmailSystem
## Parameters
- `template_name` - Name of the template in the database (e.g., "welcome_email")
- `recipient` - Email address (string) or {name, email} tuple
- `variables` - Map of variables to substitute in the template
- `opts` - Additional options:
- `:user_uuid` - Associate email with a user (for tracking)
- `:campaign_id` - Campaign identifier (for analytics)
- `:from` - Override from address (default: configured from_email)
- `:reply_to` - Reply-to address
- `:metadata` - Additional metadata map for tracking
## Returns
- `{:ok, email}` - Email sent successfully
- `{:error, :template_not_found}` - Template doesn't exist
- `{:error, :template_inactive}` - Template is not active
- `{:error, reason}` - Other error
## Examples
# Simple welcome email
PhoenixKit.Mailer.send_from_template(
"welcome_email",
"user@example.com",
%{"user_name" => "John", "url" => "https://app.com"}
)
# With user tracking
PhoenixKit.Mailer.send_from_template(
"password_reset",
{"Jane Doe", "jane@example.com"},
%{"reset_url" => "https://app.com/reset/token123"},
user_uuid: user.uuid,
campaign_id: "password_recovery"
)
# With metadata
PhoenixKit.Mailer.send_from_template(
"order_confirmation",
customer.email,
%{"order_id" => "12345", "total" => "$99.99"},
user_uuid: customer.uuid,
campaign_id: "orders",
metadata: %{order_id: order.id, amount: order.total}
)
"""
def send_from_template(template_name, recipient, variables \\ %{}, opts \\ [])
when is_binary(template_name) do
# Get the template from database
case Provider.current().get_active_template_by_name(template_name) do
nil ->
{:error, :template_not_found}
template ->
# Ensure template is active
if template.status == "active" do
# Render template with variables in the requested locale
locale = Keyword.get(opts, :locale, "en")
rendered = Provider.current().render_template(template, variables, locale)
# Build email
email =
new()
|> to(recipient)
|> from(Keyword.get(opts, :from, {get_from_name(), get_from_email()}))
|> subject(rendered.subject)
|> html_body(rendered.html_body)
|> text_body(rendered.text_body)
# Add reply-to if provided
email =
if reply_to = Keyword.get(opts, :reply_to) do
reply_to(email, reply_to)
else
email
end
# Track template usage
Provider.current().track_usage(template)
# Extract source_module from template metadata
source_module = Provider.current().get_source_module(template)
# Prepare delivery options with category and source_module from template
delivery_opts =
opts
|> Keyword.put(:template_name, template_name)
|> Keyword.put(:template_uuid, template.uuid)
|> Keyword.put_new(:campaign_id, template.category)
|> Keyword.put(:category, template.category)
|> Keyword.put_new(:source_module, source_module)
|> Keyword.put(:provider, detect_provider())
# Send email with tracking
deliver_email(email, delivery_opts)
else
{:error, :template_inactive}
end
end
end
@doc """
Delivers an email using the appropriate mailer.
If a parent application mailer is configured, delegates to it.
Otherwise uses the built-in PhoenixKit mailer.
This function also integrates with the email tracking system to log
outgoing emails when tracking is enabled.
"""
def deliver_email(email, opts \\ []) do
# Intercept email for tracking before sending
tracked_email = Provider.current().intercept_before_send(email, opts)
mailer = get_mailer()
result =
if mailer == __MODULE__ do
# Use built-in mailer with runtime config for AWS
deliver_with_runtime_config(tracked_email, mailer)
else
# Check if parent mailer also uses AWS SES
app = PhoenixKit.Config.get_parent_app()
config = Application.get_env(app, mailer, [])
if config[:adapter] == Swoosh.Adapters.AmazonSES do
# Parent mailer uses AWS SES, provide runtime config
deliver_with_runtime_config(tracked_email, mailer, app)
else
# Non-AWS mailer, use standard delivery
mailer.deliver(tracked_email)
end
end
# Handle post-send tracking updates
Provider.current().handle_after_send(tracked_email, result)
result
end
# Deliver email with runtime configuration for AWS SES
defp deliver_with_runtime_config(email, mailer, app \\ :phoenix_kit) do
config =
if app == :phoenix_kit do
# Use PhoenixKit config for built-in mailer
PhoenixKit.Config.get(mailer, [])
else
# Use parent app config for parent mailer
PhoenixKit.Config.get_parent_app_config(mailer, [])
end
# If using AWS SES, override with runtime settings from DB
runtime_config =
if config[:adapter] == Swoosh.Adapters.AmazonSES do
if Provider.current().aws_configured?() do
config
|> Keyword.put(:region, Provider.current().get_aws_region())
|> Keyword.put(:access_key, Provider.current().get_aws_access_key())
|> Keyword.put(:secret, Provider.current().get_aws_secret_key())
else
config
end
else
config
end
# Use Swoosh.Mailer.deliver with runtime config
Swoosh.Mailer.deliver(email, runtime_config)
end
@doc """
Sends a magic link email to the user.
Uses the 'magic_link' template from the database if available,
falls back to hardcoded template if not found.
## Examples
iex> PhoenixKit.Mailer.send_magic_link_email(user, "https://app.com/magic/token123")
{:ok, %Swoosh.Email{}}
"""
def send_magic_link_email(%User{} = user, magic_link_url) when is_binary(magic_link_url) do
# Variables for template substitution
template_variables = %{
"user_email" => user.email,
"magic_link_url" => magic_link_url
}
# Try to get template from database, fallback to text-only
{subject, html_body, text_body, db_template} =
case Provider.current().get_active_template_by_name("magic_link") do
nil ->
{
"Your secure login link",
nil,
magic_link_text_body(user, magic_link_url),
nil
}
template ->
rendered = Provider.current().render_template(template, template_variables)
{rendered.subject, rendered.html_body, rendered.text_body, template}
end
email =
new()
|> to({user.email, user.email})
|> from({get_from_name(), get_from_email()})
|> subject(subject)
|> html_body(html_body)
|> text_body(text_body)
# Track template usage if using database template
if db_template, do: Provider.current().track_usage(db_template)
deliver_email(email,
user_uuid: user.uuid,
template_name: "magic_link",
campaign_id: "authentication",
category: "system",
source_module: "users",
provider: detect_provider()
)
end
# Text version of the magic link email
defp magic_link_text_body(_user, magic_link_url) do
"""
Your login link: #{magic_link_url}
This link expires in 15 minutes.
"""
end
# Detect current email provider from configuration
defp detect_provider do
mailer = get_mailer()
if mailer == __MODULE__ do
detect_builtin_provider()
else
detect_parent_app_provider(mailer)
end
end
# Detect provider for built-in PhoenixKit mailer
defp detect_builtin_provider do
config = PhoenixKit.Config.get(PhoenixKit.Mailer, [])
adapter = Keyword.get(config, :adapter)
Provider.current().adapter_to_provider_name(adapter, "phoenix_kit_builtin")
end
# Detect provider for parent application mailer
defp detect_parent_app_provider(mailer) when is_atom(mailer) do
config = PhoenixKit.Config.get_parent_app_config(mailer, [])
adapter = Keyword.get(config, :adapter)
Provider.current().adapter_to_provider_name(adapter, "parent_app_mailer")
end
defp detect_parent_app_provider(_mailer), do: "unknown"
# Get the from email address from configuration or use a default
# Priority: Settings Database > Config file > Default
defp get_from_email do
# Priority 1: Settings Database (runtime)
case PhoenixKit.Settings.get_setting("from_email") do
nil ->
# Priority 2: Config file (compile-time, fallback)
case PhoenixKit.Config.get(:from_email) do
{:ok, email} -> email
# Priority 3: Default
_ -> "noreply@localhost"
end
email ->
email
end
end
# Get the from name from configuration or use a default
# Priority: Settings Database > Config file > Default
defp get_from_name do
# Priority 1: Settings Database (runtime)
case PhoenixKit.Settings.get_setting("from_name") do
nil ->
# Priority 2: Config file (compile-time, fallback)
case PhoenixKit.Config.get(:from_name) do
{:ok, name} -> name
# Priority 3: Default
_ -> "PhoenixKit"
end
name ->
name
end
end
end