Client-side rate limiting for push notifications.
Prevents exceeding provider rate limits by tracking requests locally. Uses a fixed-window counter (windows aligned to the clock, counters bumped with a single atomic ETS operation), so concurrent senders can never race the check-then-increment or clobber each other's window resets. Client-side limiting is inherently best-effort: it bounds what this node sends, and the true arbiter is always the provider.
Configuration
config :pushx,
rate_limit_enabled: true,
rate_limit_apns: 5000, # requests per window
rate_limit_fcm: 5000, # requests per window
rate_limit_window_ms: 1000 # 1 second windowUsage
Rate limiting is automatically applied when enabled. You can also check manually:
case PushX.RateLimiter.check(:apns) do
:ok -> # Proceed with sending
{:error, :rate_limited} -> # Back off
endHow It Works
- Time is divided into fixed windows of
rate_limit_window_ms - Each key (provider or named instance) gets one atomic counter per window
- When the counter passes the limit, further requests are rejected
- A new window starts a fresh counter; old windows are swept periodically
Summary
Functions
Checks if a request would be allowed without incrementing.
Checks if a request can be made and increments the counter.
Returns a specification to start this module under a supervisor.
Returns the number of requests recorded in the current window for a key.
Returns the configured limit for a provider.
Returns remaining requests before rate limit is hit.
Resets the rate limiter for a key. Useful for testing.
Resets all rate limiters.
Starts the rate limiter process.
Types
@type key() :: atom()
@type provider() :: :apns | :fcm
Functions
@spec check(provider()) :: :ok | {:error, :rate_limited}
Checks if a request would be allowed without incrementing.
@spec check_and_increment(provider()) :: :ok | {:error, :rate_limited}
Checks if a request can be made and increments the counter.
Returns :ok if under the limit, {:error, :rate_limited} if over.
The two-arity form counts under an arbitrary key (e.g. a named-instance
atom) while taking the limit from provider's config — each instance has
its own credentials and therefore its own provider-side budget.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec current_count(key()) :: non_neg_integer()
Returns the number of requests recorded in the current window for a key.
Counts attempts, including ones that were rejected over the limit.
@spec limit(provider()) :: pos_integer()
Returns the configured limit for a provider.
@spec remaining(provider()) :: non_neg_integer()
Returns remaining requests before rate limit is hit.
@spec reset(key()) :: :ok
Resets the rate limiter for a key. Useful for testing.
@spec reset_all() :: :ok
Resets all rate limiters.
Starts the rate limiter process.