PhoenixKit.Emails.RateLimiter (phoenix_kit v1.6.15)
View SourceRate 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 usersuser_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:
- Sliding Window: Tracks counts over rolling time periods
- Efficient Storage: Uses single table with automatic cleanup
- Atomic Operations: Prevents race conditions with database locks
- Memory Efficient: Automatically expires old tracking data
- 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 systemPhoenixKit.Emails.EmailInterceptor- Pre-send filteringPhoenixKit.Settings- Configuration managementPhoenixKit.Users.Auth- User-based limits and email blocking
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.
Checks if a user has custom rate limits applied.
Clears custom rate limits for a specific user.
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.
Gets comprehensive rate limit status for a specific user.
Gets monitoring events for a specific user.
Check if email address is currently blocked.
List all blocked emails with optional filtering.
Remove email address from blocklist.
Functions
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 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 system-wide rate limits.
Examples
iex> RateLimiter.check_global_limit()
:ok
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 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 if sender email address is within rate limits.
Examples
iex> RateLimiter.check_sender_limit("app@mysite.com")
:ok
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
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)
:okReturns
:ok- Limits cleared successfully
Count blocked emails with optional filtering.
Examples
iex> RateLimiter.count_blocklist()
42
iex> RateLimiter.count_blocklist(%{reason: "bounce_limit"})
15
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 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 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 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}
}
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
}
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
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 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 email address from blocklist.
Examples
iex> RateLimiter.remove_from_blocklist("user@example.com")
:ok