Search documentation of phoenix_kit

# PhoenixKit.Emails.RateLimiter (phoenix_kit v1.6.19)

View Source

Rate limiting and spam protection for the email system.

Provides multiple layers of protection against abuse, spam, and suspicious email patterns:

- Per-recipient limits - Prevent spam to individual email addresses
- Per-sender limits - Control email volume from specific senders
- Global system limits - Overall system protection
- User-specific limits - Temporary reduced limits for flagged users
- Automatic blocklists - Dynamic blocking of suspicious patterns
- Pattern detection - ML-style spam pattern recognition
- User monitoring - Event tracking for suspicious behavior

## Settings Integration

All rate limiting settings are stored in phoenix_kit_settings:

- `email_rate_limit_per_recipient` - Max emails per recipient per hour (default: 100)
- `email_rate_limit_global` - Global max emails per hour (default: 10_000)
- `email_blocklist_enabled` - Enable automatic blocklisting (default: true)

User-specific settings (stored as JSON):

- `user_rate_limits_<user_id>` - Temporary reduced limits for specific users
- `user_monitoring_<user_id>` - Event tracking log for user behavior

## Usage Examples

    # Check if sending is allowed
    case PhoenixKit.Emails.RateLimiter.check_limits(email) do
      :ok ->
        # Send email

      {:blocked, :recipient_limit} ->
        # Handle recipient rate limit

      {:blocked, :global_limit} ->
        # Handle global rate limit

      {:blocked, :blocklist} ->
        # Handle blocklisted recipient
    end

    # Flag suspicious user activity
    PhoenixKit.Emails.RateLimiter.flag_suspicious_activity(user_id, "high_bounce_rate")
    # => :flagged (user gets reduced limits for 24 hours)

    # Check user's current limit status
    status = PhoenixKit.Emails.RateLimiter.get_user_limit_status(user_id)
    # => %{has_custom_limits: true, active_recipient_limit: 10, ...}

    # Clear user's custom limits
    PhoenixKit.Emails.RateLimiter.clear_user_rate_limits(user_id)
    # => :ok

    # Add suspicious email to blocklist
    PhoenixKit.Emails.RateLimiter.add_to_blocklist(
      "spam@example.com",
      "suspicious_pattern",
      expires_at: DateTime.add(DateTime.utc_now(), 86_400)
    )

    # Check current rate limit status
    status = PhoenixKit.Emails.RateLimiter.get_rate_limit_status()
    # => %{recipient_count: 45, global_count: 2341, blocked_count: 12}

## Rate Limiting Strategy

Uses a sliding window approach with Redis-like atomic operations in PostgreSQL:

1. Sliding Window: Tracks counts over rolling time periods
2. Efficient Storage: Uses single table with automatic cleanup
3. Atomic Operations: Prevents race conditions with database locks
4. Memory Efficient: Automatically expires old tracking data
5. User-Specific Limits: JSON settings for temporary user restrictions

## User Behavior Management

- Reduced Limits: Automatically reduce limits for users with high bounce rates
- Email Blocking: Block user emails for serious violations (spam complaints)
- Activity Monitoring: Track suspicious patterns for future analysis
- Automatic Expiration: Limits and blocks expire after configured periods
- Manual Override: Admin can clear user restrictions via API

## Automatic Blocklist Features

- Pattern Detection: Identifies bulk spam patterns
- Bounce Rate Monitoring: Blocks high-bounce senders
- Complaint Rate Monitoring: Blocks high-complaint addresses
- Frequency Analysis: Detects unusual sending patterns
- Temporary Blocks: Automatic expiration of blocks
- User Integration: Links blocked emails to user accounts

## Integration Points

Integrates with:

- `PhoenixKit.Emails` - Main tracking system
- `PhoenixKit.Emails.EmailInterceptor` - Pre-send filtering
- `PhoenixKit.Settings` - Configuration management
- `PhoenixKit.Users.Auth` - User-based limits and email blocking

# Summary

## Functions

add_to_blocklist(email, reason, opts \\ [])

Add email address to blocklist.

check_blocklist(email)

Check if email address is blocklisted.

check_global_limit(period \\ :hour)

Check global system-wide rate limits.

check_limits(email_attrs)

Check all rate limits for an outgoing email.

check_recipient_limit(recipient_email, period \\ :hour)

Check if recipient email address is within rate limits.

check_sender_limit(sender_email, period \\ :hour)

Check if sender email address is within rate limits.

check_user_limits(user_id)

Checks if a user has custom rate limits applied.

clear_user_rate_limits(user_id)

Clears custom rate limits for a specific user.

count_blocklist(opts \\ %{})

Count blocked emails with optional filtering.

detect_spam_patterns(email_log)

Analyze email for suspicious spam patterns.

flag_suspicious_activity(user_id, reason)

Flag suspicious activity for a user.

get_blocklist_stats()

Get blocklist statistics.

get_rate_limit_status()

Get current rate limit status across all dimensions.

get_user_limit_status(user_id)

Gets comprehensive rate limit status for a specific user.

get_user_monitoring_events(user_id)

Gets monitoring events for a specific user.

is_blocked?(email)

Check if email address is currently blocked.

list_blocklist(opts \\ %{})

List all blocked emails with optional filtering.

remove_from_blocklist(email)

Remove email address from blocklist.

# Functions

# add_to_blocklist(email, reason, opts \\ [])

Add email address to blocklist.

## Options

- `:reason` - Reason for blocking (string)
- `:expires_at` - When block expires (DateTime, nil for permanent)
- `:user_id` - User ID that triggered the block

## Examples

    # Temporary block for 24 hours
    RateLimiter.add_to_blocklist(
      "spam@example.com",
      "bulk_spam_pattern",
      expires_at: DateTime.add(DateTime.utc_now(), 86_400)
    )

    # Permanent block
    RateLimiter.add_to_blocklist("malicious@example.com", "manual_block")

# check_blocklist(email)

Check if email address is blocklisted.

## Examples

    iex> RateLimiter.check_blocklist("user@example.com")
    :ok

    iex> RateLimiter.check_blocklist("spam@blocked.com")
    {:blocked, :blocklist}

# check_global_limit(period \\ :hour)

Check global system-wide rate limits.

## Examples

    iex> RateLimiter.check_global_limit()
    :ok

# check_limits(email_attrs)

Check all rate limits for an outgoing email.

Returns `:ok` if email can be sent, or `{:blocked, reason}` if blocked.

## Examples

    iex> RateLimiter.check_limits(%{to: "user@example.com", from: "app@mysite.com"})
    :ok

    iex> RateLimiter.check_limits(%{to: "blocked@spam.com"})
    {:blocked, :blocklist}

# check_recipient_limit(recipient_email, period \\ :hour)

Check if recipient email address is within rate limits.

## Examples

    iex> RateLimiter.check_recipient_limit("user@example.com")
    :ok

    iex> RateLimiter.check_recipient_limit("high-volume@example.com")
    {:blocked, :recipient_limit}

# check_sender_limit(sender_email, period \\ :hour)

Check if sender email address is within rate limits.

## Examples

    iex> RateLimiter.check_sender_limit("app@mysite.com")
    :ok

# check_user_limits(user_id)

Checks if a user has custom rate limits applied.

Returns user's custom limits if they exist and haven't expired, otherwise returns nil.

## Examples

    iex> RateLimiter.check_user_limits(123)
    %{
      "recipient_limit" => 10,
      "sender_limit" => 50,
      "reason" => "high_bounce_rate",
      "applied_at" => "2025-01-15T12:00:00Z",
      "expires_at" => "2025-01-16T12:00:00Z"
    }

    iex> RateLimiter.check_user_limits(999)
    nil

# clear_user_rate_limits(user_id)

Clears custom rate limits for a specific user.

Removes any reduced limits or custom restrictions applied to the user, returning them to default system limits.

## Examples

    iex> RateLimiter.clear_user_rate_limits(123)
    :ok

## Returns

- `:ok` - Limits cleared successfully

# count_blocklist(opts \\ %{})

Count blocked emails with optional filtering.

## Examples

    iex> RateLimiter.count_blocklist()
    42

    iex> RateLimiter.count_blocklist(%{reason: "bounce_limit"})
    15

# detect_spam_patterns(email_log)

Analyze email for suspicious spam patterns.

Returns a list of detected patterns or empty list if clean.

## Examples

    iex> RateLimiter.detect_spam_patterns(email_log)
    []

    iex> RateLimiter.detect_spam_patterns(suspicious_email_log)
    ["high_frequency", "bulk_template"]

# flag_suspicious_activity(user_id, reason)

Flag suspicious activity for a user.

Automatically triggers blocklist or rate limit adjustments based on activity patterns.

## Examples

    iex> RateLimiter.flag_suspicious_activity(123, "high_bounce_rate")
    :flagged

    iex> RateLimiter.flag_suspicious_activity(456, "complaint_spam")
    :blocked

# get_blocklist_stats()

Get blocklist statistics.

Returns a map with statistics about blocked emails.

## Examples

    iex> RateLimiter.get_blocklist_stats()
    %{
      total_blocks: 42,
      active_blocks: 38,
      expired_today: 4,
      by_reason: %{"manual_block" => 10, "bounce_limit" => 28, ...}
    }

# get_rate_limit_status()

Get current rate limit status across all dimensions.

## Examples

    iex> RateLimiter.get_rate_limit_status()
    %{
      global: %{count: 1250, limit: 10_000, percentage: 12.5},
      recipients: %{active_limits: 5, total_emails: 892},
      senders: %{active_limits: 2, total_emails: 1250},
      blocklist: %{active_blocks: 15, expired_today: 3}
    }

# get_user_limit_status(user_id)

Gets comprehensive rate limit status for a specific user.

Returns a map with user's current limits, monitoring status, and any active restrictions.

## Examples

    iex> RateLimiter.get_user_limit_status(123)
    %{
      user_id: 123,
      has_custom_limits: true,
      custom_limits: %{"recipient_limit" => 10, "sender_limit" => 50},
      monitoring: %{"event_count" => 5, "last_event_at" => "..."},
      is_blocked: false,
      default_recipient_limit: 100,
      default_sender_limit: 1000
    }

    iex> RateLimiter.get_user_limit_status(999)
    %{
      user_id: 999,
      has_custom_limits: false,
      custom_limits: nil,
      monitoring: nil,
      is_blocked: false,
      default_recipient_limit: 100,
      default_sender_limit: 1000
    }

# get_user_monitoring_events(user_id)

Gets monitoring events for a specific user.

Returns the monitoring log with all tracked events for the user, or nil if no monitoring exists.

## Examples

    iex> RateLimiter.get_user_monitoring_events(123)
    %{
      "events" => [
        %{"event_type" => "bulk_sending", "timestamp" => "...", "metadata" => %{...}},
        %{"event_type" => "high_bounce_rate", "timestamp" => "...", "metadata" => %{...}}
      ],
      "event_count" => 2,
      "first_event_at" => "2025-01-15T12:00:00Z",
      "last_event_at" => "2025-01-15T18:00:00Z"
    }

    iex> RateLimiter.get_user_monitoring_events(999)
    nil

# is_blocked?(email)

Check if email address is currently blocked.

## Examples

    iex> RateLimiter.is_blocked?("user@example.com")
    false

    iex> RateLimiter.is_blocked?("blocked@spam.com")
    true

# list_blocklist(opts \\ %{})

List all blocked emails with optional filtering.

## Options

- `:search` - Search term for email address
- `:reason` - Filter by block reason
- `:include_expired` - Include expired blocks (default: false)
- `:limit` - Limit number of results
- `:offset` - Offset for pagination
- `:order_by` - Order field (:email, :inserted_at, :expires_at)
- `:order_dir` - Order direction (:asc, :desc)

## Examples

    iex> RateLimiter.list_blocklist()
    [%EmailBlocklist{}, ...]

    iex> RateLimiter.list_blocklist(%{reason: "manual_block", limit: 10})
    [%EmailBlocklist{}, ...]

# remove_from_blocklist(email)

Remove email address from blocklist.

## Examples

    iex> RateLimiter.remove_from_blocklist("user@example.com")
    :ok