# `PhoenixKit.Modules.Emails.Log`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L1)

Email 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 line
- `headers`: 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 used
- `campaign_id`: Campaign or group identifier for analytics
- `attachments_count`: Number of email attachments
- `size_bytes`: Total email size in bytes
- `retry_count`: Number of send retry attempts
- `error_message`: Error message if sending failed
- `status`: Current status (queued, sent, delivered, bounced, opened, clicked, failed, etc.)
- `queued_at`: Timestamp when email was queued for sending
- `sent_at`: Timestamp when email was sent
- `delivered_at`: Timestamp when email was delivered (from provider)
- `rejected_at`: Timestamp when email was rejected by provider
- `failed_at`: Timestamp when email send failed
- `delayed_at`: Timestamp when email delivery was delayed
- `bounced_at`: Timestamp when email bounced
- `complained_at`: Timestamp when spam complaint was received
- `opened_at`: Timestamp when email was first opened
- `clicked_at`: Timestamp when first link was clicked
- `configuration_set`: AWS SES configuration set used
- `message_tags`: JSONB tags for grouping and analytics
- `provider`: Email provider used (aws_ses, smtp, local, etc.)

## 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 EmailEvent
```

### Benefits 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

```elixir
# 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_id
```

### Database Constraints

- `message_id`: UNIQUE NOT NULL
- `aws_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 filters
- `get_log!/1` - Get an email log by ID (raises if not found)
- `get_log_by_message_id/1` - Get log by message ID from provider
- `create_log/1` - Create a new email log
- `update_log/2` - Update an existing email log
- `update_status/2` - Update log status with timestamp
- `delete_log/1` - Delete an email log

### Status Management
- `mark_as_queued/1` - Mark email as queued with timestamp
- `mark_as_sent/1` - Mark email as sent with timestamp
- `mark_as_delivered/2` - Mark email as delivered with timestamp
- `mark_as_bounced/3` - Mark as bounced with bounce type and reason
- `mark_as_rejected/2` - Mark as rejected by provider
- `mark_as_failed/2` - Mark as failed with error reason
- `mark_as_delayed/2` - Mark as delayed with delay information
- `mark_as_opened/2` - Mark as opened with timestamp
- `mark_as_clicked/3` - Mark as clicked with link and timestamp

### Analytics Functions
- `get_stats_for_period/2` - Get statistics for date range
- `get_campaign_stats/1` - Get statistics for specific campaign
- `get_engagement_metrics/1` - Calculate open/click rates
- `get_provider_performance/1` - Provider-specific metrics
- `get_bounce_analysis/1` - Detailed bounce analysis

### System Functions
- `cleanup_old_logs/1` - Remove logs older than specified days
- `compress_old_bodies/1` - Compress body_full for old emails
- `get_logs_for_archival/1` - Get logs ready for archival

## Usage Examples

    # Create a new email log
    {:ok, log} = PhoenixKit.Modules.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.Modules.Emails.Log.mark_as_delivered(
      log, UtilsDate.utc_now()
    )

    # Get campaign statistics
    stats = PhoenixKit.Modules.Emails.Log.get_campaign_stats("newsletter_2024")

# `changeset`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L230)

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.

# `cleanup_old_logs`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L935)

Removes emails older than specified number of days.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.cleanup_old_logs(90)
    {5, nil}  # Deleted 5 records

# `compress_old_bodies`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L951)

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.Modules.Emails.Log.compress_old_bodies(30)
    {12, nil}  # Compressed 12 records

# `count_logs`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L342)

Counts emails with optional filtering (without loading all records).

## Parameters

- `filters` - Map of filters to apply (optional)

## Examples

    iex> PhoenixKit.Modules.Emails.Log.count_logs(%{status: "bounced"})
    42

# `create_log`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L486)

Creates an email log.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.create_log(%{message_id: "abc", to: "user@test.com"})
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

    iex> PhoenixKit.Modules.Emails.Log.create_log(%{message_id: ""})
    {:error, %Ecto.Changeset{}}

# `delete_log`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L745)

Deletes an email log.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.delete_log(log)
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

    iex> PhoenixKit.Modules.Emails.Log.delete_log(log)
    {:error, %Ecto.Changeset{}}

# `find_by_aws_message_id`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L441)

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.Modules.Emails.Log.find_by_aws_message_id("abc123-aws")
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

    iex> PhoenixKit.Modules.Emails.Log.find_by_aws_message_id("nonexistent")
    {:error, :not_found}

# `get_campaign_stats`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L793)

Gets statistics for a specific campaign.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.get_campaign_stats("newsletter_2024")
    %{total_sent: 500, delivery_rate: 96.0, open_rate: 25.0, click_rate: 5.0}

# `get_daily_delivery_trends`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L863)

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.Modules.Emails.Log.get_daily_delivery_trends(:last_7_days)
    %{
      labels: ["2024-09-01", "2024-09-02", ...],
      delivered: [120, 190, 300, ...],
      bounced: [5, 10, 15, ...]
    }

# `get_engagement_metrics`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L831)

Gets engagement metrics for analysis.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.get_engagement_metrics(:last_30_days)
    %{avg_open_rate: 24.5, avg_click_rate: 4.2, engagement_trend: :increasing}

# `get_log`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L364)

Gets a single email log by ID or UUID.

Accepts integer ID, UUID string, or string-formatted integer.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.get_log(123)
    %PhoenixKit.Modules.Emails.Log{}

    iex> PhoenixKit.Modules.Emails.Log.get_log("550e8400-e29b-41d4-a716-446655440000")
    %PhoenixKit.Modules.Emails.Log{}

    iex> PhoenixKit.Modules.Emails.Log.get_log(999)
    nil

# `get_log!`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L388)

Same as `get_log/1`, but raises `Ecto.NoResultsError` if not found.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.get_log!("018f1234-5678-7890-abcd-ef1234567890")
    %PhoenixKit.Modules.Emails.Log{}

    iex> PhoenixKit.Modules.Emails.Log.get_log!("00000000-0000-0000-0000-000000000000")
    ** (Ecto.NoResultsError)

# `get_log_by_message_id`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L408)

Gets a single email log by message ID from the email provider.

Returns nil if not found.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.get_log_by_message_id("msg-abc123")
    %PhoenixKit.Modules.Emails.Log{}

    iex> PhoenixKit.Modules.Emails.Log.get_log_by_message_id("nonexistent")
    nil

# `get_logs_for_archival`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L969)

Gets logs ready for archival to external storage.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.get_logs_for_archival(90)
    [%PhoenixKit.Modules.Emails.Log{}, ...]

# `get_provider_performance`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L896)

Gets provider-specific performance metrics.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.get_provider_performance(:last_7_days)
    %{"aws_ses" => %{delivered: 98.5, bounced: 1.5}, "smtp" => %{delivered: 95.0, bounced: 5.0}}

# `get_stats_for_period`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L759)

Gets statistics for a specific time period.

## Examples

    iex> PhoenixKit.Modules.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}

# `list_logs`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L321)

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)
- `:user_uuid` - Filter by associated user UUID
- `:limit` - Limit number of results (default: 50)
- `:offset` - Offset for pagination

## Examples

    iex> PhoenixKit.Modules.Emails.Log.list_logs(%{status: "bounced", limit: 10})
    [%PhoenixKit.Modules.Emails.Log{}, ...]

# `mark_as_bounced`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L546)

Marks an email as bounced with type and reason.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.mark_as_bounced(log, "hard", "No such user")
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

# `mark_as_clicked`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L613)

Marks an email as clicked with link URL and timestamp.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.mark_as_clicked(log, "https://example.com", UtilsDate.utc_now())
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

# `mark_as_delayed`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L724)

Marks an email as delayed with delay information.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.mark_as_delayed(log, "Temporary mailbox unavailable")
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

# `mark_as_delivered`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L529)

Marks an email as delivered with timestamp.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.mark_as_delivered(log, UtilsDate.utc_now())
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

# `mark_as_failed`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L690)

Marks an email as failed with error reason.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.mark_as_failed(log, "Connection timeout")
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

# `mark_as_opened`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L586)

Marks an email as opened with timestamp.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.mark_as_opened(log, UtilsDate.utc_now())
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

# `mark_as_queued`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L638)

Marks an email as queued with timestamp.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.mark_as_queued(log)
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

# `mark_as_rejected`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L672)

Marks an email as rejected by provider with reason.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.mark_as_rejected(log, "Invalid recipient")
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

# `mark_as_sent`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L655)

Marks an email as sent with timestamp.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.mark_as_sent(log)
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

# `update_log`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L503)

Updates an email log.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.update_log(log, %{status: "delivered"})
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

    iex> PhoenixKit.Modules.Emails.Log.update_log(log, %{to: ""})
    {:error, %Ecto.Changeset{}}

# `update_status`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.66/lib/modules/emails/log.ex#L517)

Updates the status of an email log.

## Examples

    iex> PhoenixKit.Modules.Emails.Log.update_status(log, "delivered")
    {:ok, %PhoenixKit.Modules.Emails.Log{}}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
