PhoenixKit.Emails.RateLimiter (phoenix_kit v1.6.3)
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
- 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:
- 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
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:
PhoenixKit.Emails- Main tracking systemPhoenixKit.Emails.EmailInterceptor- Pre-send filteringPhoenixKit.Settings- Configuration managementPhoenixKit.Users.Auth- User-based limits
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 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
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}
}
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