Limits and quotas
A relay on the internet will meet a client that opens a thousand sockets, or one that pushes to the same device as fast as it can. The relay limits both by default, without any configuration from you.
Pushes carry no sender identity, so the relay can’t limit by sender. Every limit is keyed on the recipient’s inbox, on a device’s key, or on a total count. None is keyed on an IP address. The relay never logs, stores or uses a client’s IP address (Running a relay has the wording for your users). If you want per-IP limits, set them in your firewall or reverse proxy.
Defaults
| Limit | Default | When it’s reached |
|---|---|---|
| Messages waiting per inbox | 10,000 Blobs | The push is refused as inbox full. The sender retries later. |
| Bytes waiting per inbox | 268,435,456 bytes (256 MiB) | The push is refused as inbox full. The sender retries later. |
| Push rate per recipient | 20 pushes per second sustained, bursts of 500 | The push is refused as rate limited. The sender retries later. |
| Open connections per port | 1,000 connections | The new connection is closed. |
| Receive Sessions per device key | 4 Receive Sessions | The new session is refused. |
| Message size | The Protocol Size Limit | The push is refused as too large, for good. |
| How long a message waits | 30 days | The message is deleted. |
A refused push carries a short code and no content. The sending device keeps a message refused as inbox full or rate limited in its outbox and tries again with backoff. A too large refusal is permanent, and the sending app gets sendFailed(messageId, tooLarge). Delivery issues covers the app side.
Deadlines
| Step | Deadline |
|---|---|
| Noise handshake, on either port | 10 seconds |
| A whole Push Session, from connect to close | 30 seconds |
| A Receive Session with nothing received | 75 seconds, then the relay closes it |
Devices send a heartbeat every 25 seconds on their Receive Session and the relay echoes it, so a healthy device never hits the 75-second limit.
How long the relay keeps a message
The relay keeps a message until the recipient device acknowledges it, which the device does after your app’s handler has finished with it. A message nobody acknowledges is deleted after 30 days. A sweep runs every hour and deletes what has expired.
A device that stays offline past that point never gets those messages, and neither device is told. Apps should expect that a device away for over a month comes back having missed some.
Changing the limits
You can change every value. Two of them can only go down:
- The message size limit, called the Relay Size Limit when you set it. The relay refuses to start if it’s above the Protocol Size Limit. If you lower it, pass a matching lower
maxPayloadBytestoopen()in your app, sosend()refuses large bodies before they’re queued. - The TTL. The relay refuses to start with a TTL over 30 days. That ceiling keeps a message sent on the last day a device would retry it inside the Replay Window (60 days), the window in which devices accept it.
| Setting | Variable | Format |
|---|---|---|
| TTL | TRUESEAL_RELAY_TTL | A duration such as 168h |
| Sweep interval | TRUESEAL_RELAY_REAP_INTERVAL | A duration such as 1h |
| Open connections per port | TRUESEAL_RELAY_MAX_CONNECTIONS | A number |
| Relay Size Limit | TRUESEAL_RELAY_MAX_ENVELOPE_BYTES | Bytes of the sealed envelope, which is larger than the message body your app sends |
The quota attack
Anyone who knows a device’s public key can push to it until its inbox is full or its rate limit is hit. Messages for that device are then delayed until the inbox drains. The relay doesn’t prevent this. It is an attack on availability for that one device, and the messages stay sealed.
What stays your job
- Per-IP limits, in your firewall or reverse proxy.
- Telling your app about a Relay Size Limit lower than the Protocol Size Limit.
- Telling your users that a device offline for longer than the TTL misses messages.