PhoenixKit.Emails.Log (phoenix_kit v1.6.4)
View SourceEmail logging for PhoenixKit - comprehensive logging in a single module.
This module provides both the Ecto schema definition and business logic for managing emails. It includes email creation tracking, status updates, event relationships, and analytics functions.
Schema Fields
message_id: Internal unique identifier (pk_XXXXX format) generated before sending (required, unique)aws_message_id: AWS SES message ID from provider response (optional, unique when present)to: Recipient email address (required)from: Sender email address (required)subject: Email subject lineheaders: JSONB map of email headers (without duplication)body_preview: Preview of email content (first 500+ characters)body_full: Complete email body content (optional, settings-controlled)template_name: Name/identifier of email template usedcampaign_id: Campaign or group identifier for analyticsattachments_count: Number of email attachmentssize_bytes: Total email size in bytesretry_count: Number of send retry attemptserror_message: Error message if sending failedstatus: Current status (queued, sent, delivered, bounced, opened, clicked, failed, etc.)queued_at: Timestamp when email was queued for sendingsent_at: Timestamp when email was sentdelivered_at: Timestamp when email was delivered (from provider)rejected_at: Timestamp when email was rejected by providerfailed_at: Timestamp when email send faileddelayed_at: Timestamp when email delivery was delayedbounced_at: Timestamp when email bouncedcomplained_at: Timestamp when spam complaint was receivedopened_at: Timestamp when email was first openedclicked_at: Timestamp when first link was clickedconfiguration_set: AWS SES configuration set usedmessage_tags: JSONB tags for grouping and analyticsprovider: Email provider used (aws_ses, smtp, local, etc.)user_id: Associated user ID for authentication emails
Message ID Strategy
PhoenixKit uses a dual message ID strategy to handle the lifecycle of email tracking:
1. Internal Message ID (message_id)
- Format:
pk_XXXXX(PhoenixKit prefix + random hex) - Generated: BEFORE email is sent (in EmailInterceptor)
- Purpose: Primary identifier for database operations
- Uniqueness: Always unique, never null
- Usage: Used in logs, events, and internal correlation
2. AWS SES Message ID (aws_message_id)
- Format: Provider-specific (e.g., AWS SES format)
- Generated: AFTER email is sent (from provider response)
- Purpose: Correlation with AWS SES events (SNS/SQS)
- Uniqueness: Unique when present, nullable
- Usage: Used to match SQS events to email logs
Workflow
1. Email Created
└─> EmailInterceptor generates message_id (pk_12345)
└─> EmailLog created with message_id = "pk_12345"
└─> aws_message_id = nil (not yet sent)
2. Email Sent via AWS SES
└─> AWS returns MessageId = "0102abc-def-ghi"
└─> EmailInterceptor updates:
- message_id stays "pk_12345" (unchanged)
- aws_message_id = "0102abc-def-ghi"
- message_tags stores both for debugging
3. SQS Event Received
└─> Event contains AWS MessageId = "0102abc-def-ghi"
└─> SQSProcessor searches:
a) First by message_id (if starts with pk_)
b) Then by aws_message_id field
c) Then in headers/metadata
└─> Updates EmailLog and creates EmailEventBenefits of Dual Strategy
- Early Tracking: Can create logs before provider response
- Event Correlation: AWS message_id links to SQS events
- Robustness: Multiple search strategies prevent missed events
- Debugging: Both IDs stored in message_tags for troubleshooting
- No Duplication: Partial unique index prevents duplicate aws_message_id
Search Priority in SQSProcessor
# 1. Direct message_id search (for internal IDs)
get_log_by_message_id(message_id)
# 2. AWS message_id field search (for provider IDs)
find_by_aws_message_id(aws_message_id)
# 3. Metadata search (fallback for legacy data)
# searches in headers for aws_message_idDatabase Constraints
message_id: UNIQUE NOT NULLaws_message_id: PARTIAL UNIQUE (WHERE aws_message_id IS NOT NULL)- Composite index: (message_id, aws_message_id) for fast correlation
Core Functions
Email Log Management
list_logs/1- Get emails with optional filtersget_log!/1- Get an email log by ID (raises if not found)get_log_by_message_id/1- Get log by message ID from providercreate_log/1- Create a new email logupdate_log/2- Update an existing email logupdate_status/2- Update log status with timestampdelete_log/1- Delete an email log
Status Management
mark_as_queued/1- Mark email as queued with timestampmark_as_sent/1- Mark email as sent with timestampmark_as_delivered/2- Mark email as delivered with timestampmark_as_bounced/3- Mark as bounced with bounce type and reasonmark_as_rejected/2- Mark as rejected by providermark_as_failed/2- Mark as failed with error reasonmark_as_delayed/2- Mark as delayed with delay informationmark_as_opened/2- Mark as opened with timestampmark_as_clicked/3- Mark as clicked with link and timestamp
Analytics Functions
get_stats_for_period/2- Get statistics for date rangeget_campaign_stats/1- Get statistics for specific campaignget_engagement_metrics/1- Calculate open/click ratesget_provider_performance/1- Provider-specific metricsget_bounce_analysis/1- Detailed bounce analysis
System Functions
cleanup_old_logs/1- Remove logs older than specified dayscompress_old_bodies/1- Compress body_full for old emailsget_logs_for_archival/1- Get logs ready for archival
Usage Examples
# Create a new email log
{:ok, log} = PhoenixKit.Emails.Log.create_log(%{
message_id: "msg-abc123",
to: "user@example.com",
from: "noreply@myapp.com",
subject: "Welcome to MyApp",
template_name: "welcome_email",
campaign_id: "welcome_series",
provider: "aws_ses"
})
# Update status when delivered
{:ok, updated_log} = PhoenixKit.Emails.Log.mark_as_delivered(
log, DateTime.utc_now()
)
# Get campaign statistics
stats = PhoenixKit.Emails.Log.get_campaign_stats("newsletter_2024")
Summary
Functions
Returns an %Ecto.Changeset{} for tracking email log changes.
Creates a changeset for email log creation and updates.
Removes emails older than specified number of days.
Compresses body_full field for logs older than specified days. Sets body_full to nil to save storage space while keeping body_preview.
Counts emails with optional filtering (without loading all records).
Creates an email log from a Swoosh.Email struct.
Creates an email log.
Deletes an email log.
Finds an email log by AWS message ID.
Gets statistics for a specific campaign.
Gets daily delivery trend data for charts.
Gets engagement metrics for analysis.
Gets a single email log by ID.
Gets a single email log by message ID from the email provider.
Gets logs ready for archival to external storage.
Gets provider-specific performance metrics.
Gets statistics for a specific time period.
Returns a list of emails with optional filters.
Marks an email as bounced with type and reason.
Marks an email as clicked with link URL and timestamp.
Marks an email as delayed with delay information.
Marks an email as delivered with timestamp.
Marks an email as failed with error reason.
Marks an email as opened with timestamp.
Marks an email as queued with timestamp.
Marks an email as rejected by provider with reason.
Marks an email as sent with timestamp.
Updates an email log.
Updates the status of an email log.
Validates email format using basic regex pattern.
Functions
Returns an %Ecto.Changeset{} for tracking email log changes.
Examples
iex> PhoenixKit.Emails.Log.change_log(log)
%Ecto.Changeset{data: %PhoenixKit.Emails.Log{}}
Creates a changeset for email log creation and updates.
Validates required fields and ensures data consistency. Automatically sets sent_at on new records if not provided.
Removes emails older than specified number of days.
Examples
iex> PhoenixKit.Emails.Log.cleanup_old_logs(90)
{5, nil} # Deleted 5 records
Compresses body_full field for logs older than specified days. Sets body_full to nil to save storage space while keeping body_preview.
Examples
iex> PhoenixKit.Emails.Log.compress_old_bodies(30)
{12, nil} # Compressed 12 records
Counts emails with optional filtering (without loading all records).
Parameters
filters- Map of filters to apply (optional)
Examples
iex> PhoenixKit.Emails.Log.count_logs(%{status: "bounced"})
42
Creates an email log from a Swoosh.Email struct.
Extracts relevant data and creates an appropriately formatted log entry.
Examples
iex> email = new() |> to("user@example.com") |> from("app@example.com")
iex> PhoenixKit.Emails.Log.create_from_swoosh_email(email, provider: "aws_ses")
{:ok, %PhoenixKit.Emails.Log{}}
Creates an email log.
Examples
iex> PhoenixKit.Emails.Log.create_log(%{message_id: "abc", to: "user@test.com"})
{:ok, %PhoenixKit.Emails.Log{}}
iex> PhoenixKit.Emails.Log.create_log(%{message_id: ""})
{:error, %Ecto.Changeset{}}
Deletes an email log.
Examples
iex> PhoenixKit.Emails.Log.delete_log(log)
{:ok, %PhoenixKit.Emails.Log{}}
iex> PhoenixKit.Emails.Log.delete_log(log)
{:error, %Ecto.Changeset{}}
Finds an email log by AWS message ID.
This function looks for logs where the AWS SES message ID might be stored in the message_id field after sending.
Examples
iex> PhoenixKit.Emails.Log.find_by_aws_message_id("abc123-aws")
{:ok, %PhoenixKit.Emails.Log{}}
iex> PhoenixKit.Emails.Log.find_by_aws_message_id("nonexistent")
{:error, :not_found}
Gets statistics for a specific campaign.
Examples
iex> PhoenixKit.Emails.Log.get_campaign_stats("newsletter_2024")
%{total_sent: 500, delivery_rate: 96.0, open_rate: 25.0, click_rate: 5.0}
Gets daily delivery trend data for charts.
Returns daily statistics optimized for chart visualization including delivery trends and bounce patterns over the specified period.
Examples
iex> PhoenixKit.Emails.Log.get_daily_delivery_trends(:last_7_days)
%{
labels: ["2024-09-01", "2024-09-02", ...],
delivered: [120, 190, 300, ...],
bounced: [5, 10, 15, ...]
}
Gets engagement metrics for analysis.
Examples
iex> PhoenixKit.Emails.Log.get_engagement_metrics(:last_30_days)
%{avg_open_rate: 24.5, avg_click_rate: 4.2, engagement_trend: :increasing}
Gets a single email log by ID.
Raises Ecto.NoResultsError if the log does not exist.
Examples
iex> PhoenixKit.Emails.Log.get_log!(123)
%PhoenixKit.Emails.Log{}
iex> PhoenixKit.Emails.Log.get_log!(999)
** (Ecto.NoResultsError)
Gets a single email log by message ID from the email provider.
Returns nil if not found.
Examples
iex> PhoenixKit.Emails.Log.get_log_by_message_id("msg-abc123")
%PhoenixKit.Emails.Log{}
iex> PhoenixKit.Emails.Log.get_log_by_message_id("nonexistent")
nil
Gets logs ready for archival to external storage.
Examples
iex> PhoenixKit.Emails.Log.get_logs_for_archival(90)
[%PhoenixKit.Emails.Log{}, ...]
Gets provider-specific performance metrics.
Examples
iex> PhoenixKit.Emails.Log.get_provider_performance(:last_7_days)
%{"aws_ses" => %{delivered: 98.5, bounced: 1.5}, "smtp" => %{delivered: 95.0, bounced: 5.0}}
Gets statistics for a specific time period.
Examples
iex> PhoenixKit.Emails.Log.get_stats_for_period(~U[2024-01-01 00:00:00Z], ~U[2024-01-31 23:59:59Z])
%{total_sent: 1500, delivered: 1450, bounced: 30, opened: 800, clicked: 200}
Returns a list of emails with optional filters.
Filters
:status- Filter by status (sent, delivered, bounced, etc.):campaign_id- Filter by campaign:template_name- Filter by template:provider- Filter by email provider:from_date- Emails sent after this date:to_date- Emails sent before this date:recipient- Filter by recipient email (supports partial match):limit- Limit number of results (default: 50):offset- Offset for pagination
Examples
iex> PhoenixKit.Emails.Log.list_logs(%{status: "bounced", limit: 10})
[%PhoenixKit.Emails.Log{}, ...]
Marks an email as bounced with type and reason.
Examples
iex> PhoenixKit.Emails.Log.mark_as_bounced(log, "hard", "No such user")
{:ok, %PhoenixKit.Emails.Log{}}
Marks an email as clicked with link URL and timestamp.
Examples
iex> PhoenixKit.Emails.Log.mark_as_clicked(log, "https://example.com", DateTime.utc_now())
{:ok, %PhoenixKit.Emails.Log{}}
Marks an email as delayed with delay information.
Examples
iex> PhoenixKit.Emails.Log.mark_as_delayed(log, "Temporary mailbox unavailable")
{:ok, %PhoenixKit.Emails.Log{}}
Marks an email as delivered with timestamp.
Examples
iex> PhoenixKit.Emails.Log.mark_as_delivered(log, DateTime.utc_now())
{:ok, %PhoenixKit.Emails.Log{}}
Marks an email as failed with error reason.
Examples
iex> PhoenixKit.Emails.Log.mark_as_failed(log, "Connection timeout")
{:ok, %PhoenixKit.Emails.Log{}}
Marks an email as opened with timestamp.
Examples
iex> PhoenixKit.Emails.Log.mark_as_opened(log, DateTime.utc_now())
{:ok, %PhoenixKit.Emails.Log{}}
Marks an email as queued with timestamp.
Examples
iex> PhoenixKit.Emails.Log.mark_as_queued(log)
{:ok, %PhoenixKit.Emails.Log{}}
Marks an email as rejected by provider with reason.
Examples
iex> PhoenixKit.Emails.Log.mark_as_rejected(log, "Invalid recipient")
{:ok, %PhoenixKit.Emails.Log{}}
Marks an email as sent with timestamp.
Examples
iex> PhoenixKit.Emails.Log.mark_as_sent(log)
{:ok, %PhoenixKit.Emails.Log{}}
Updates an email log.
Examples
iex> PhoenixKit.Emails.Log.update_log(log, %{status: "delivered"})
{:ok, %PhoenixKit.Emails.Log{}}
iex> PhoenixKit.Emails.Log.update_log(log, %{to: ""})
{:error, %Ecto.Changeset{}}
Updates the status of an email log.
Examples
iex> PhoenixKit.Emails.Log.update_status(log, "delivered")
{:ok, %PhoenixKit.Emails.Log{}}
Validates email format using basic regex pattern.