forge-docs

0016. Notification Service Rate Limiting Strategy

Status: Accepted Date: 2026-01-23 Context: Outbound rate limiting for SES, Twilio, and future providers so limits are respected and critical messages are not starved.

Context

The Notification Service sends notifications to external providers (AWS SES, Twilio, etc.), each with different API rate limits and constraints:

The existing libs/security rate limiting framework (RateLimitingFilter) is designed for incoming HTTP request rate limiting (per-user, per-service, per-IP). This is fundamentally different from what the notification service needs: outgoing notification rate limiting to external providers with provider-specific constraints.

Requirements:


Decision

The Notification Service will implement per-provider rate limiting with priority-aware throttling.

Rate Limiting Strategy:

  1. Per-Provider Rate Limiting
    • Each provider (AWS SES, Twilio, etc.) has its own rate limiter
    • Provider-specific limits configured per provider
    • Limits respect external service constraints
  2. Priority-Aware Throttling
    • CRITICAL notifications: Bypass or have very high limits (e.g., 1000/min)
    • HIGH notifications: High limits (e.g., 500/min)
    • NORMAL notifications: Standard limits (e.g., 100/min)
    • LOW notifications: Lower limits (e.g., 50/min), may be throttled further under load
  3. Capacity Management
    • When system load > 80%: Throttle LOW priority notifications
    • When system load > 95%: Pause LOW priority processing
    • CRITICAL and HIGH always processed regardless of load

Implementation:


Rationale

Why Per-Provider (Not Per-Channel or Per-Priority Only):

  1. Provider Constraints - Each provider has different limits (SES ≠ Twilio)
  2. Granularity - More granular than per-channel (multiple providers per channel)
  3. Accuracy - Respects actual external service constraints
  4. Flexibility - Can add new providers without affecting existing ones

Why Not Use Existing libs/security Rate Limiting:

  1. Different Purpose - Existing framework is for incoming HTTP requests, not outgoing provider calls
  2. Provider-Specific - Need provider-specific limits, not user/service-based
  3. Priority-Aware - Need priority-based throttling, not just flat limits
  4. Custom Logic - Requires custom logic for provider failover and retry

Why Priority-Aware:

  1. Critical Notifications - Password resets, account lockouts must not be blocked
  2. Graceful Degradation - Low-priority notifications can be delayed during high load
  3. Resource Optimization - Process important notifications first
  4. User Experience - Critical notifications always get through

Consequences

Positive:

Negative / Tradeoffs:

Mitigations:


Implementation

Rate Limiter Structure:

@ApplicationScoped
public class ProviderRateLimiter {
    private final Map<String, RateLimiter> providerLimiters;
    
    public boolean tryConsume(String provider, NotificationPriority priority) {
        RateLimiter limiter = providerLimiters.get(provider);
        // Priority-aware: CRITICAL bypasses, LOW respects strict limits
        return limiter.tryConsume(priority);
    }
}

Configuration:

# AWS SES Rate Limits
notification.rate-limit.ses.critical.per-minute=1000
notification.rate-limit.ses.high.per-minute=500
notification.rate-limit.ses.normal.per-minute=100
notification.rate-limit.ses.low.per-minute=50

# Twilio Rate Limits (when implemented)
notification.rate-limit.twilio.critical.per-minute=1000
notification.rate-limit.twilio.high.per-minute=500
notification.rate-limit.twilio.normal.per-minute=100
notification.rate-limit.twilio.low.per-minute=50

Integration:



Future Considerations

Distributed Rate Limiting:

Dynamic Rate Limit Detection: