PhoenixKitEmails
Email tracking, analytics, and AWS SES integration for PhoenixKit. Drop-in email logging, delivery event tracking, engagement metrics, templating, rate limiting, and an admin dashboard.
Features
- Email logging — comprehensive logging of all outgoing emails with optional body/header storage
- AWS SES integration — SQS-based event polling for delivery, bounce, complaint, open, and click events
- Analytics & metrics — system stats, engagement rates, campaign performance, and provider comparison
- Email templates — create and manage reusable email templates with a LiveView editor
- Rate limiting — configurable protection against abuse and spam via Hammer
- Blocklist — manage blocked email addresses
- Queue management — view and manage outgoing email queue
- Archival — automatic cleanup, body compression, and S3 archival of old email data
- Webhook processing — process incoming delivery/bounce/complaint webhooks
- CSV export — export email logs via controller endpoint
- Email open/click tracking — pixel tracking and link rewriting for engagement metrics
- Admin dashboard — LiveView pages for logs, metrics, templates, queue, blocklist, and settings
- Auto-discovery — implements
PhoenixKit.Modulebehaviour; PhoenixKit finds it at startup with zero config
⚠️ Email templates are moving to files
Message templates are moving out of phoenix_kit_email_templates and into
files a host owns in its own repository — version-controlled and reviewable,
rather than edited through an admin UI and invisible to code review. Core reads
them through phoenix_kit_templates.
Nothing has changed yet. The database layer still wins at send time and every customized template works exactly as it does today. But a later release drops the table, and edits that have not been exported will be lost then.
If you have customized any email template:
mix phoenix_kit_emails.templates.export # --dry-run to preview first
Review the diff and commit the files. Nothing is deleted and no row is touched, so an export that turns out wrong costs nothing but the files.
The task exports only what you actually edited. A template still identical to what this package ships is skipped, because core supplies those itself now, translated into every shipped locale. On an install that never opened the template editor it writes nothing at all, and you can stop reading here.
Newsletter layouts (is_system: false, picked per broadcast) are not affected
— those are authored at runtime by people who cannot open a pull request, so
they keep a table and an editor.
What the files look like
The template name becomes a directory; each part is a file, and any locale that is not the fallback carries its code:
priv/phoenix_kit_templates/
└── register/
├── subject.txt # the fallback locale — what everyone falls through to
├── subject.uk.txt
├── text.txt
└── html.html
Installation
Add phoenix_kit_emails to your dependencies in mix.exs:
def deps do
[
{:phoenix_kit_emails, "~> 0.2"}
]
end
Then fetch dependencies:
mix deps.get
Note: For development or if not yet published to Hex, you can use:
{:phoenix_kit_emails, github: "BeamLabEU/phoenix_kit_emails"}
PhoenixKit auto-discovers the module at startup — no additional configuration needed.
Quick Start
- Add the dependency to
mix.exs - Run
mix deps.get - Enable the module in admin settings (
email_enabled: true) - Configure your AWS SES configuration set name in settings
- Email logs and analytics are available at
/admin/emails
Usage
System management
alias PhoenixKit.Modules.Emails
# Check if system is enabled
Emails.enabled?()
# Enable/disable the system
Emails.enable_system()
Emails.disable_system()
# Get current configuration
config = Emails.get_config()
Email logging
# Create a log entry
{:ok, log} = Emails.create_log(%{
to: "user@example.com",
from: "noreply@example.com",
subject: "Welcome!",
status: "sent"
})
# List logs with filters
logs = Emails.list_logs(%{status: "sent", page: 1})
# Get a specific log
log = Emails.get_log!(log_uuid)
Analytics
# Get system statistics
stats = Emails.get_system_stats(:last_30_days)
# => %{total_sent: 5000, delivered: 4850, bounce_rate: 2.5, open_rate: 23.4}
# Get campaign performance
campaign_stats = Emails.get_campaign_stats("newsletter_2024")
# => %{total_sent: 1000, delivery_rate: 98.5, open_rate: 25.2, click_rate: 4.8}
# Get engagement metrics
metrics = Emails.get_engagement_metrics(:last_7_days)
# Get provider performance comparison
performance = Emails.get_provider_performance(:last_30_days)
Webhook processing
# Process an incoming webhook from AWS SES
{:ok, event} = Emails.process_webhook_event(webhook_data)
# List events for a specific log
events = Emails.list_events_for_log(log_uuid)
Maintenance
# Clean up old logs (by retention days)
{deleted_count, _} = Emails.cleanup_old_logs(90)
# Compress old email bodies to save storage
Emails.compress_old_bodies(30)
# Archive old data to S3
Emails.archive_to_s3(180)
Settings
Settings are managed through the PhoenixKit Settings API and can be configured via the admin UI at /admin/settings/email-sending.
| Key | Type | Default | Description |
|---|---|---|---|
email_enabled | boolean | false | Enable/disable the email system |
email_save_body | boolean | false | Save full email body (vs preview only) |
email_save_headers | boolean | false | Save email headers |
email_ses_events | boolean | false | Manage AWS SES delivery events |
email_retention_days | integer | 90 | Days to keep email logs |
aws_ses_configuration_set | string | — | AWS SES configuration set name |
email_compress_body | integer | — | Compress body after N days |
email_archive_to_s3 | boolean | false | Enable S3 archival |
email_s3_bucket | string | — | S3 bucket archives are written to |
email_s3_integration | string | — | Integrations UUID that signs uploads (unset = ExAws chain) |
email_sampling_rate | integer | — | Percentage of emails to fully log |
email_create_placeholder_logs | boolean | false | Create placeholder logs for orphaned events |
Permissions
The module declares permissions via permission_metadata/0:
"emails"— access to email admin dashboard and all sub-pages
Use Scope.has_module_access?/2 to check permissions in your application.
Architecture
lib/
mix/tasks/
phoenix_kit_emails.install.ex # Install mix task
phoenix_kit/modules/emails/
emails.ex # Context + PhoenixKit.Module behaviour
application_integration.ex # Application-level integration
archiver.ex # S3 archival logic
archive_worker.ex # Hourly Oban cron that calls the archiver
email_log_data.ex # Email log data struct
event.ex # Delivery/bounce/complaint event schema
interceptor.ex # Email interceptor for logging
log.ex # Email log schema
metrics.ex # Analytics and metrics queries
paths.ex # Centralized URL path helpers
provider.ex # Provider behaviour
rate_limiter.ex # Rate limiting via Hammer
sqs_polling_job.ex # Oban job for SQS polling
sqs_polling_manager.ex # SQS polling lifecycle management
sqs_processor.ex # SQS message processing
supervisor.ex # OTP Supervisor
table_columns.ex # Admin table column definitions
template.ex # Email template schema
templates.ex # Template management context
utils.ex # Shared utilities
web/
blocklist.ex # Blocklist admin LiveView
details.ex # Email log detail LiveView
emails.ex # Email logs admin LiveView
email_tracking.ex # Open/click tracking
export_controller.ex # CSV export controller
metrics.ex # Metrics dashboard LiveView
queue.ex # Queue management LiveView
routes.ex # Route definitions
settings.ex # Settings admin LiveView
template_editor.ex # Template editor LiveView
templates.ex # Templates admin LiveView
webhook_controller.ex # Webhook endpoint controller
Database Tables
| Table | Description |
|---|---|
phoenix_kit_email_logs | Email log records (UUIDv7 PK) |
phoenix_kit_email_events | Delivery/bounce/complaint/open/click events |
phoenix_kit_email_templates | Reusable email templates |
Development
mix deps.get # Install dependencies
mix test # Run tests
mix format # Format code
mix credo --strict # Static analysis (strict mode)
mix dialyzer # Type checking
mix docs # Generate documentation
mix precommit # Compile + format + credo + dialyzer
mix quality # Format + credo + dialyzer
Troubleshooting
Emails not appearing in admin
- Verify
email_enabledistruein settings - Ensure the module is listed as a dependency in the parent app's
mix.exs
SES events not processing
- Verify
email_ses_eventsis enabled in settings - Check that
aws_ses_configuration_setis configured - Ensure SQS queue permissions allow the application to poll
- Review Oban dashboard for failed SQS polling jobs
Metrics showing zero data
- Confirm emails are being logged (check
/admin/emails) - Verify the date range filter matches when emails were sent
License
MIT — see LICENSE for details.