# `PushX.Retry`
[🔗](https://github.com/cignosystems/pushx/blob/v0.11.0/lib/push_x/retry.ex#L1)

Retry logic for push notification delivery following Apple and Google best practices.

## Retry Strategy

Based on official Apple APNS and Google FCM documentation:

- **Connection errors**: Retry with exponential backoff (10s, 20s, 40s)
- **Server errors (5xx)**: Retry with exponential backoff
- **Rate limited (429)**: Respect `retry-after` header, or default to 60 seconds
- **Permanent failures**: Do not retry (bad token, payload too large, etc.)

## Configuration

    config :pushx,
      retry_enabled: true,
      retry_max_attempts: 3,
      retry_base_delay_ms: 10_000,  # 10 seconds (Google recommends minimum 10s)
      retry_max_delay_ms: 60_000    # 60 seconds max

## References

- Apple: https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server
- Google: https://firebase.google.com/docs/cloud-messaging/scale-fcm

# `calculate_delay`

```elixir
@spec calculate_delay(PushX.Response.t(), pos_integer(), pos_integer(), pos_integer()) ::
  pos_integer()
```

Calculates the delay before the next retry attempt.

- For rate limiting: Uses retry_after value or 60 seconds default
- For connection errors: Faster retry (1s base) since these are transient
- For server errors: Standard exponential backoff from config

## Exponential Backoff Formula

    delay = min(base_delay * 2^(attempt-1) + jitter, max_delay)

# `retryable?`

```elixir
@spec retryable?(PushX.Response.t()) :: boolean()
```

Returns true if the error is retryable.

Retryable errors:
- `:connection_error` - Network/connection failure
- `:rate_limited` - Too many requests (with backoff)
- `:server_error` - Provider server error (5xx)

Non-retryable (permanent) errors:
- `:invalid_token` - Device token is invalid
- `:expired_token` - Device token has expired
- `:unregistered` - Device is no longer registered
- `:payload_too_large` - Payload exceeds size limit
- `:unknown_error` - Unrecognized error (could be client-side issue)

# `with_retry`

```elixir
@spec with_retry(
  (-&gt; {:ok, PushX.Response.t()} | {:error, PushX.Response.t()}),
  keyword()
) :: {:ok, PushX.Response.t()} | {:error, PushX.Response.t()}
```

Executes a function with retry logic.

The function should return `{:ok, response}` or `{:error, response}`.
Retries are only attempted for retryable errors.

## Options

  * `:max_attempts` - Maximum number of attempts (default: 3)
  * `:base_delay_ms` - Base delay in milliseconds (default: 10_000)
  * `:max_delay_ms` - Maximum delay in milliseconds (default: 60_000)

## Examples

    PushX.Retry.with_retry(fn -> PushX.APNS.send_once(token, payload, opts) end)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
