0.1.14 - 2026-07-19
Fixed
Emails.aws_configured?/0always returnedtrueregardless of actual configuration:get_aws_access_key/0/get_aws_secret_key/0returnnil(not"") when unconfigured, but the check only compared against""(nil != ""istruein Elixir). This silently affectedsync_email_status/1,fetch_sqs_events_for_message/1,fetch_dlq_events_for_message/1,current_provider/0, and the "AWS Configured" badge on the Amazon SES & SQS settings section — all now correctly reflect whether AWS credentials are actually present. (#19)
Changed
SQSPollingJobnow gates polling on whether SES is actually the thing sending mail right now, mirroringBrevoPollingJob's sender-aware gate: polling requires either an enabledSendProfilepointed at an"aws_ses"integration, orEmails.aws_configured?/0as an explicit override for deployments that predate theSendProfilesystem (legacy Settings/env-var credentials, or a bareaws_sesIntegrations connection with nothing pointed at it). (#19)
0.1.13 - 2026-07-19
Fixed
- The
/webhooks/sesroute was piped through the host app's:browserpipeline, which (per the documented default) includesprotect_from_forgery. AWS SNS delivers webhook notifications as a cold, session-less POST with no CSRF token, so every notification 403'd withPlug.CSRFProtection.InvalidCSRFTokenErrorbefore ever reachingWebhookController. The route now runs through its own minimal:phoenix_kit_emails_webhookpipeline (justplug :accepts, ["html"], no session/CSRF plugs), mirroring the equivalent fix inphoenix_kit_newsletters's one-click-unsubscribe route. (#17)
0.1.12 - 2026-07-19
Changed
- AWS SES credentials now resolve through
PhoenixKit.Integrations(an encryptedaws_sesconnection) instead of being stored as plaintext Settings rows.get_aws_access_key/0,get_aws_secret_key/0, andget_aws_region/0prefer the selected Integrations connection and fall back to the legacy Settings/env-var path, so an unmigrated install keeps sending.migrate_legacy/0moves an existing key/secret into a new connection once, idempotently, without deleting the legacy Settings rows (an operator confirms the new connection works before blanking them manually). - The combined credential lookup is cached for 60s (
PhoenixKit.Cache) so building a send-path AWS config no longer costs 3 Settings reads plus 3 decrypt round-trips per email;invalidate_aws_credentials_cache/0is called wherever the selected connection changes. - Settings moved off this module's own
/admin/settings/emailstab and now contributes two sections ("Email Tracking", "Amazon SES & SQS") to core's unified/admin/settings/email-sendingpage viaemail_settings_sections/0.settings.ex/settings.html.heexare gone, replaced byweb/settings_sections/. - Requires
phoenix_kit ~> 1.7.190(the release that carries theemail_settings_sections/0seam).
Fixed
- SES bounce classification matched
"Temporary"for soft bounces, but SES actually sends"Transient"— every soft bounce was silently recorded as a hard one. Both strings are now accepted. - Hard (permanent) bounces are now added to the rate limiter's blocklist, so a bounced address stops receiving future sends instead of bouncing again on the next campaign.
PhoenixKit.Modules.Emails.ProviderimplementsPhoenixKit.Email.Providerbut never declared@behaviour/@impl— a renamed or dropped callback in core would have compiled clean here and only failed at runtime. Declared, with@implon all 14 callbacks.- Boot-time
migrate_legacy/0no longer makes a live SES API call (GetSendQuota, up to 15s) to validate the migrated connection — the credentials were already sending mail before the migration ran, so there was nothing to verify that the first real send wouldn't; this was blocking app startup on network egress. mix hex.publishrefused to build the package withhackneydeclared asoverride: true("Can't build package with overridden dependency hackney, removeoverride: true"). The override dates back to whenex_aws_sqspinnedhackney ~> 1.9; since 0.1.11 swapped that forbeamlab_ex_aws_sqs(which declares no hackney dependency at all), nothing in the tree needs hackney forced above its natural resolution — removed,mix.lockunchanged (still resolves 4.6.0).
Added
- First real test infrastructure for this package (
test/support/data_case.ex,test_repo.ex,config/test.exs) — the credential-resolution and SQS bounce/blocklist paths now have DB-backed integration tests instead of being untestable.
0.1.11 - 2026-07-12
Security
ex_aws_sqsreplaced withbeamlab_ex_aws_sqs, matching the switch already made in core (phoenix_kit1.7.188/189).ex_aws_sqs(last released Jan 2023, since archived upstream) pinshackney ~> 1.9, which was blocking thehackney ~> 4.0upgrade needed to clear a batch of hackney CVEs and mademix hex.auditfail on everyprecommit/release. The fork is a maintained drop-in with the same public API (ExAws.SQS) and no hackney dependency, but switches SQS from the legacy Query/XML protocol to AWS's JSON protocol, which changes response shapes (%{"Messages" => [...]}with string keys like"ReceiptHandle", instead of%{body: %{messages: [...]}}with atom keys).SQSPollingJobalready matched both shapes defensively;Emails.poll_sqs_for_message/5andpoll_dlq_for_message/5(used by the email-details "find delivery events" lookup) only matched the old shape and were updated to match both.mix hex.auditnow reports zero advisories. Pulls inphoenix_kit~> 1.7.189 andex_aws2.7.x as part of the same hackney 4.x resolution.
0.1.10 - 2026-07-12
Changed
- Emails admin UI: Settings/Dashboard now share phoenix_kit's core
<.input>/<.checkbox>components instead of hand-rolled markup, section headers sized to match core's other Settings pages, and the breadcrumb reads "Settings / Emails" instead of "Emails Settings". - AWS Region is now a static searchable dropdown (backed by the
aws_regionspackage, now a direct dependency) instead of manual entry plus a "Load regions" AWS API call.
Fixed
- SES Configuration Set, SNS Topic ARN, and SQS Queue URL/ARN/DLQ settings were only visible in the AWS Configuration card after enabling "AWS SES Events Options", even though "Setup AWS Infrastructure" could already populate them without that toggle — they're now always visible/editable alongside the rest of AWS Configuration.
- The Dashboard's "System Status" card showed a hardcoded "Active" badge regardless of whether email delivery was actually configured.
Added
- Mailer adapter transparency:
Utils.mailer_adapter_status/0detects the real Swoosh adapter using the same built-in/delegated-mailer resolution logic asPhoenixKit.Maileritself, and both Settings and the Dashboard now show what's actually configured — plus a copy-pasteableconfig.exssnippet when it's missing or isn't Amazon SES — instead of silently assuming AWS SES everywhere.
0.1.9 - 2026-07-08
Fixed
Event.create_event/1unconditionally inserted withmode: :savepoint(added in 0.1.8 to protectLog.mark_as_opened/2/mark_as_clicked/3's transactional callers).:savepointmode is not a no-op outside a transaction — it requires one to nest a savepoint in, and raisesDBConnection.TransactionError: transaction is not startedotherwise. Every event created by the SQS processor's non-transactional paths (delivery, bounce, complaint, open, click) was hitting this, so thephoenix_kit_email_eventsaudit trail silently stopped populating for messages processed via SQS. Fixed by only requesting:savepointmode whenrepo().in_transaction?()is true.
Changed
- Dependency bumps (
mix.lock):phoenix_kit1.7.172 → 1.7.178, plus patch-level updates tophoenix,phoenix_live_view,db_connection,swoosh,mint, and others.
0.1.8 - 2026-06-24
Added
- Admin UI overhaul for the Emails module: page title/subtitle moved into the admin shell top bar; a daisyUI table toolbar across Emails/Templates/Queue/Blocklist (dropdown filters, a persistent inline search with inline clear, action buttons); clickable column-header sorting (server-side, URL-backed, field-whitelisted) on the contexts that support ordering; body-row click-to-open (Emails/Queue → Details, Templates → edit); a drag-and-drop column customizer on Emails; and a "Get update on this email" per-row status sync. (#13)
Send Testnow renders and sends the seededtest_emailsystem template (falling back to a built-in body) and recordstemplate_name, the sending admin'suser_uuid, andsource_module: "emails", so the email Details page shows the template, user, and module. (#13)
Fixed
- Webhook security (SNS): the signing-certificate URL is locked to
sns.<region>.amazonaws.comwith a/SimpleNotificationService-*.pempath before any fetch (blocks forged-cert signature bypass and SSRF);SignatureVersionis honored (SHA-1 vs SHA-256);X-Forwarded-Foris trusted only from configured proxies (newwebhook_trusted_proxiessetting, default empty); andconfirm_subscription/1actually issues the SubscribeURL GET. (#12) - Event ingestion: open/click events no longer overwrite terminal statuses (bounced/complaint/rejected/failed); event creation is idempotent via DB unique constraints with graceful
{:ok, :duplicate_event}mapping; open/click dedup onoccurred_atpreserves multiple distinct engagements while collapsing exact SQS redeliveries;opened_at/clicked_atare now recorded. (#12) - SQS pipeline: placeholder logs are inserted directly, bypassing the sampling roll that previously returned
{:ok, :skipped}, crashed callers, and re-cycled messages forever; the Oban poller is collapsed to exactly one self-healing chain (always self-schedules while enabled, backs off on misconfiguration instead of dying);Task.yield_manyprevents a slow task from aborting a whole batch; sub-second polling intervals are rejected. (#12) - The dedup insert in
Event.create_event/1runs withmode: :savepoint, so a unique-constraint violation insideLog.mark_as_opened/2/mark_as_clicked/3's transaction no longer aborts the transaction and silently rolls back the status update. - Analytics:
get_stats_for_period/2now counts thecomplaintstatus (it previously matched acomplainedstring that is never written, so the metric was always 0) and runs as one grouped query instead of seven aggregate round-trips. (#12) - The Emails table column customizer validates column ids against the available-column set before persisting, so a crafted client event can no longer store an unknown column. (#13)
- List sorting applies a deterministic UUID (primary-key) tiebreaker, so rows with equal primary-sort values page consistently across the Emails, Templates, and Blocklist lists. (#13)
Changed
- Archiver body compression truly streams via
Repo.streamin a transaction (was loading the full result set and only compressing the first batch); CSV exports route every cell through formula-injection + RFC 4180 escaping;list_logsno longer preloads[:user, :events]on the admin hot path; PubSub status updates refresh a single row instead of reloading the list. (#12) - Adopt
phoenix_kit1.7.165 (provides the core migration backing the email-event dedup unique indexes).
0.1.7 - 2026-06-23
Added
- Live-update the Emails admin list on delivery-status changes via PubSub.
Log.update_log/2broadcasts a lightweight{:email_log_updated, …}event only when a log's status actually changes; the emails LiveView refreshes just the affected on-screen row (no 30-day stats recompute). Best-effort — a PubSub failure can never break the DB write. (#11)
Fixed
- Consolidate SQS polling onto a single Oban-based poller: remove the legacy 842-line
SQSWorkerGenServer.SQSPollingJob+SQSPollingManagerare now the sole poller and runtime control surface (enable/disable without an app restart). (#11) - Self-scheduling no longer stalls: the polling job's
uniqueconstraint excludes running jobs, so an executing job can enqueue its successor and a crash-orphaned job can't permanently block new inserts. (#11) - SQS messages no longer re-cycle forever:
delete_message/3returns failures instead of swallowing them as:ok, reads both string and atom receipt-handle keys, and won't count an undeleted message as processed. (#11) - The admin SQS-polling toggle now starts/stops the poller at runtime (routed through
SQSPollingManager) instead of only persisting the flag and waiting for the next boot. (#11) - Provider detection classifies a message as
aws_seswhen an SES configuration set is configured, even when the host app sends through its own Swoosh mailer. (#11) - Post-merge review fixes: compile clean under
--warnings-as-errorswith Oban 2.23 (narrow the polling job'suniquestates to[:scheduled]), satisfycredo --strictfor the new broadcast helper, and drop a stale retiredearmarkentry from the lockfile.
Changed
- Refresh dependency lockfile (notable bumps:
oban→ 2.23,phoenix_kit→ 1.7.164,phoenix_live_view→ 1.2,swoosh→ 1.26,tesla→ 1.20,bandit→ 1.12,req).
0.1.6 - 2026-05-25
Added
- Route all 9 Emails admin LiveViews through the per-module
PhoenixKit.Modules.Emails.Gettextbackend, so this package'sru/etcatalogues resolve at render time instead of falling back to English. Extends gettext coverage across the full template surface (IAM/SES setup walkthrough, template editor, blocklist, metrics, queue);default.potgrows to 447 msgids. (#9) - Localise ~100
put_flashmessages across the Emails LiveViews (settings toggles, errors, confirmations) with%{var}interpolation bindings anden/ru/ettranslations. (#10)
Fixed
- Correct gettext catalogue mis-fills from the bulk regeneration: 12
enentries wheremsgstr≠msgid, and 7ru/etcross-locale mistranslations (e.g. the "Setup AWS Infrastructure" button rendering a stray "3." step prefix; Archive/Clone template tooltips reading "New Template";Queuedstatus showing "Queue").
Changed
- Require
phoenix_kit ~> 1.7.106(per-module Gettext backend API). - Refresh dependency lockfile (notable bumps:
phoenix_kit1.7.108→1.7.120,ecto/ecto_sql3.13→3.14,fresco0.1→0.6 plus newetcher,tesla1.17→1.18,hammer7.3→7.4,bandit,plug,req).
0.1.5 - 2026-05-12
Added
- Wrap Emails settings and email tracking UI strings in
gettext(47 new msgids indefault.pot,en,ru,etcatalogues). Covers tracking-options toggle labels, data retention block, privacy notice, current configuration table, tracking-benefits headers, IAM/SES setup walkthrough, and remaining placeholders.
Changed
- Widen Emails settings page to use the full container width on wide screens (drop the
max-w-4xl mx-autowrapper). Short numeric inputs keep theirmax-w-xscaps. - Refresh dependency lockfile to latest compatible versions (notable bumps:
finch0.21→0.22,postgrex0.22.1→0.22.2,swoosh1.25.1→1.25.2,phoenix_kit1.7.106→1.7.108,telemetry1.4.1→1.4.2).
0.1.4 - 2026-05-08
Added
- Per-module Gettext backend (
PhoenixKit.Modules.Emails.Gettext) withen/ru/etcatalogues for all admin sidebar tab labels. Requiresphoenix_kitrelease that ships thegettext_backendTab API (BeamLabEU/phoenix_kit#522); on older releases tabs render raw English (graceful degradation).
Fixed
- Suppress
EmailInterceptorLogger warnings when the configured Swoosh adapter is not AWS SES.
Changed
- Refresh dependency lockfile to latest compatible versions (notable bumps:
bandit,db_connection,decimal,ecto,ex_doc).
0.1.3 - 2026-04-12
Fixed
- Add routing anti-pattern warning to AGENTS.md
All notable changes to this project will be documented in this file.
This project adheres to Semantic Versioning.
0.1.2 - 2026-04-02
Fixed
- Changed
css_sources/0return type from atom to binary to matchPhoenixKit.Modulebehaviour callback spec.
Changed
- Rewrote README to match sibling project structure with full documentation.
0.1.1 - 2026-03-27
Fixed
- Removed
@behaviourand@implannotations fromProviderto fix compilation warnings (behaviour defined in host app). - Suppressed
Hammerundefined module warning inWebhookController.
Changed
- Rewrote install task to automatically add Tailwind CSS
@sourcedirective toapp.css(idempotent). - Updated
.gitignorewith standard Elixir project entries.
0.1.0 - 2026-03-24
Added
- Initial extraction from PhoenixKit core into a standalone package.
PhoenixKit.Email.Providerbehaviour with 14 callbacks.- AWS SES integration for email sending via SMTP and API.
- AWS SNS webhook processing for bounce, complaint, and delivery notifications.
- AWS SQS polling for asynchronous event ingestion.
- Email tracking and analytics (opens, clicks, deliveries, bounces, complaints).
- 9 admin LiveViews: dashboard, logs, templates, campaigns, recipients, settings, domains, blocklist, tracking.
- Email template management with variable interpolation.
- CSV and JSON export for email logs and analytics.
- Swoosh interceptor for automatic email tracking.
- Rate limiting on webhook endpoints via Hammer.
- SNS signature verification for webhook security.
- CSV formula injection protection in exports.
- Install mix task (
mix phoenix_kit_emails.install).