PhoenixKit.Emails.RateLimiter (phoenix_kit v1.6.3)

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
  • Automatic blocklists - Dynamic blocking of suspicious patterns
  • Pattern detection - ML-style spam pattern recognition

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)

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

# 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

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

Integration Points

Integrates with:

Summary

Functions

Add email address to blocklist.

Check if email address is blocklisted.

Check global system-wide rate limits.

Check all rate limits for an outgoing email.

Check if recipient email address is within rate limits.

Check if sender email address is within rate limits.

Count blocked emails with optional filtering.

Analyze email for suspicious spam patterns.

Flag suspicious activity for a user.

Get blocklist statistics.

Get current rate limit status across all dimensions.

Check if email address is currently blocked.

List all blocked emails with optional filtering.

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

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}
}

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