# Limits and troubleshooting

> Size and audience limits, delivery pace, how push tokens are kept and removed, and what to do when a push doesn't arrive.

Source: https://docs.grovs.io/docs/engagement/push-campaigns/limits-and-troubleshooting

## Limits

| What                      | Limit                                                                                                                                                                                     |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Title                     | 255 characters                                                                                                                                                                            |
| Body                      | 1,000 characters                                                                                                                                                                          |
| Whole notification        | 4,096 bytes (Apple's limit), including image URL and custom data. Checked for every variant.                                                                                              |
| Custom data               | 2 KB                                                                                                                                                                                      |
| Image                     | PNG, JPEG or GIF. Uploads up to 1 MB; URLs must be `https://`.                                                                                                                            |
| Sound name                | 64 characters                                                                                                                                                                             |
| User ID or device ID list | 10,000 entries, each up to 256 bytes                                                                                                                                                      |
| Devices per user ID       | 50                                                                                                                                                                                        |
| Schedule                  | Up to 30 days ahead                                                                                                                                                                       |
| Per-device delivery rows  | Kept 30 days after the send. Report counts are kept for good.                                                                                                                             |
| Server API                | Per project per minute: 600 campaigns, 10 segment campaigns, 100,000 recipients. See [Send from your server](https://docs.grovs.io/docs/engagement/push-campaigns/send-from-your-server). |

### Daily cap

**Pushes per person per day** in **Engage › Engagement Settings** caps pushes per person per UTC day, from 1 to 50. It covers every campaign, journey and server push in the project, and is off by default.

A push over the cap isn't sent and fails with reason `capped`. The count is per device, so someone with two phones has a separate count on each.

## Delivery pace

Grovs hands pushes to Apple and Google only a few seconds ahead of sending. That keeps **Cancel** accurate and the report near real time. A campaign to a million devices takes around 10 minutes.

Campaigns sending at the same time, from any project, take turns of a few seconds each. A large campaign slows a small one but never blocks it.

## How Grovs handles push tokens

The SDK sends your app's push token to Grovs with the device's details. Grovs keeps one token per device.

- **Updates.** Pass the token every time your app gets one. A new token replaces the old one.
- **Missing token.** A request that omits the token or sends `null` keeps the stored token.
- **Dead tokens.** On Apple `Unregistered` or `BadDeviceToken`, or FCM `404` for an unregistered device, Grovs removes the token. The next campaign counts the device as **No channel** instead of failing again. Only the failed token is removed; a newer token registered meanwhile is kept.
- **Test environment.** A `BadDeviceToken` from the APNs sandbox usually means a production token reached the Grovs test environment, so the test environment keeps the token. In production, `BadDeviceToken` removes it.

## Common failures

| Reason in the report                                                   | Likely cause                                                                             | Fix                                                                                                           |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Push not configured for this platform (`no_app`)                       | No APNs key or Firebase credentials for that platform.                                   | Add them in **Developers › SDK Setup**, iOS or Android, **Push Notifications** step.                          |
| Device has no push token (`no_token`)                                  | The token was removed between the audience freeze and delivery.                          | None; the device is skipped.                                                                                  |
| `Unregistered` / FCM `404`                                             | App uninstalled or token expired.                                                        | None; Grovs removes the token.                                                                                |
| `BadDeviceToken`                                                       | A token from the other APNs environment, often an Xcode build getting a production push. | Send to Xcode builds from the Grovs test environment, and to TestFlight and App Store builds from production. |
| `InvalidProviderToken`, `ExpiredProviderToken`, `MissingProviderToken` | Apple can't verify your APNs key: wrong Key ID or Team ID, or a revoked key.             | Upload a valid `.p8` key with the right Key ID, and check the Apple App Prefix (Team ID).                     |
| `BadTopic`, `DeviceTokenNotForTopic`, `TopicDisallowed`                | The token belongs to a different app or bundle ID than the one configured.               | Check the bundle ID in **Developers › SDK Setup › iOS**.                                                      |
| `PayloadTooLarge`                                                      | Over Apple's size limit.                                                                 | Shorten the body, custom data or image URL.                                                                   |
| FCM `401` / `403`                                                      | Firebase rejected your credentials, or the sender isn't allowed for this token.          | Upload a service account key from the same Firebase project as your app, and check the Firebase Project ID.   |
| `capped`                                                               | The person reached the daily cap.                                                        | Raise or turn off the cap in **Engagement Settings**.                                                         |
| `app_removed`                                                          | Push credentials were changed or removed while the push waited.                          | Resend with **Duplicate** if needed.                                                                          |

## Troubleshooting

### Delivered, but nothing shows on the phone

**Delivered** means Apple or Google accepted the push and will deliver it when the phone is online. It still won't show if:

- the person turned off notifications for your app
- a Focus mode hides it
- the app never asked for notification permission. On Android 13 and later, request `POST_NOTIFICATIONS`.

### The image is missing

- On iOS, your app needs a Notification Service Extension. See [Campaign images](https://docs.grovs.io/docs/sdk/ios/push-notifications#4-campaign-images-optional).
- Use PNG, JPEG or GIF. WebP and other formats are skipped and the push arrives without an image.
- Image URLs must be `https://`.

### Opened stays at zero

- Tap tracking needs iOS SDK 3.1.0 or later, and your app must pass taps to `Grovs.handleNotificationResponse`. See [Push notifications on iOS](https://docs.grovs.io/docs/sdk/ios/push-notifications).
- On Android, tap tracking needs Android SDK 3.1.0 or later, with taps reaching `Grovs.onStart`/`onNewIntent` or `Grovs.handleNotificationOpen`. See [Push notifications on Android](https://docs.grovs.io/docs/sdk/android/push-notifications).
- Only a tap on the notification counts, not a dismissal or a custom action button.

### Many devices count as No channel

These devices have no push token, or are web or desktop. Check that your app registers for notifications and passes the token to the SDK on every launch, and that people allowed notifications.

### A campaign stays in Evaluating

Large segments take longer. If too many segment queries are running, Grovs retries for about three hours, then fails the campaign with **Segment busy**.

### A campaign stays in Sending (self-hosted)

The `rpush` process sends pushes to Apple and Google. If it isn't running, nothing leaves the queue. Check that the Enterprise stack's `rpush` service is up.
