# SmartAppPush — Server Integration Guide (Firebase Cloud Messaging)

> **For AI coding tools.** Drop this file into your backend project and reference it when implementing SmartAppPush push notifications from your server. This guide is for apps where the mobile client uses Firebase Cloud Messaging (FCM) as the push delivery provider.
>
> Official docs: https://smartapppush.ai/docs

## What is the Developer Push API?

SmartAppPush's Developer API lets you send push notifications directly from your server with a single HTTP call. No dashboard, no campaigns needed. Use it for transactional and real-time notifications triggered by your backend logic.

**Use cases:**
- Order status updates — "Your order has shipped!"
- Real-time alerts — "New message from Sarah"
- Payment confirmations — "Payment of $49.99 received"
- Backend-triggered reminders — "Your reservation is in 1 hour"

> For bulk or recurring sends (e.g., re-engagement, promotions), use SmartAppPush Campaigns via the dashboard instead of the Developer API.

**How delivery works with FCM:** When you send a push via this API, SmartAppPush delivers it as a **data-only FCM message** to the user's devices. The mobile app's `FirebaseMessagingService` receives the message and is responsible for building and displaying the notification. This gives the mobile app full control over notification appearance and behavior.

---

## Configuration

You need this value from the SmartAppPush dashboard:

| Key | Where to find | Usage |
|-----|--------------|-------|
| `server_key` | Dashboard → App → Settings | Authenticates server-to-server API calls |

**Base URL:** `https://api.smartapppush.ai`

> **`server_key` is a secret.** Do not expose it in client-side code, mobile apps, or public repositories. Store it in environment variables or a secrets manager.

---

## Send Push to User

### Endpoint

```
POST https://api.smartapppush.ai/developer/push
Content-Type: application/json
X-Server-Key: <your_server_key>
```

Authentication: `X-Server-Key` header with your app's server key.

### Request Example

```json
{
  "app_user_id": "user_123",
  "title": "Your order has shipped!",
  "body": "Order #4521 is on its way. Estimated delivery: March 31.",
  "extra_data": {
    "order_id": "4521",
    "screen": "order_detail",
    "tracking_url": "https://track.example.com/4521"
  },
  "schedule_at": "2026-03-29T15:00:00Z",
  "priority": 8,
  "dedupe_key": "order-shipped-4521"
}
```

### Minimal Request (immediate send)

```json
{
  "app_user_id": "user_123",
  "title": "Hello",
  "body": "World"
}
```

### cURL Examples

**Full request with scheduling:**

```bash
curl -X POST https://api.smartapppush.ai/developer/push \
  -H "Content-Type: application/json" \
  -H "X-Server-Key: <server_key>" \
  -d '{
    "app_user_id": "user_123",
    "title": "Your order shipped!",
    "body": "Track your package now.",
    "extra_data": {"order_id": "456", "deep_link": "/orders/456"},
    "schedule_at": "2026-03-28T15:00:00Z",
    "priority": 7,
    "dedupe_key": "order-shipped-456"
  }'
```

**Minimal request (immediate send):**

```bash
curl -X POST https://api.smartapppush.ai/developer/push \
  -H "Content-Type: application/json" \
  -H "X-Server-Key: <server_key>" \
  -d '{
    "app_user_id": "user_123",
    "title": "Hello",
    "body": "World"
  }'
```

### Response

```json
// 201 Created
{
  "push_count": 2,
  "app_user_id": "user_123",
  "app_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
```

- `push_count` — Number of devices the push was sent to (one user can have multiple devices)
- `app_user_id` — The user the push was sent to
- `app_id` — Your app's UUID

### Field Reference

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `app_user_id` | string | **Yes** | The user to send the push to. Must match exactly what the mobile app sends in events |
| `title` | string | **Yes** | Push notification title |
| `body` | string | **Yes** | Push notification body text |
| `extra_data` | object | No | Custom key-value data delivered in the push payload. The mobile app receives this when the user taps the notification — use for deep linking, screen navigation, or passing context |
| `schedule_at` | string | No | RFC 3339 timestamp for scheduled delivery (e.g., `2026-03-29T15:00:00Z`). Sends immediately if omitted |
| `priority` | integer | No | 1 (lowest) to 10 (highest). Default: 5 |
| `dedupe_key` | string | No | Prevents duplicate pushes. Same key within a 10-minute window → second push is silently skipped. Auto-generated if omitted |

### Error Responses

| Status | Body | Meaning |
|--------|------|---------|
| `400` | `{"errors": ["app_user_id is required", "title is required"]}` | Missing required fields or invalid format |
| `401` | `{"error": "missing X-Server-Key header"}` | No `X-Server-Key` header provided |
| `401` | `{"error": "invalid server key"}` | The server key doesn't match any app |
| `404` | `{"error": "no devices with push permission found for this user"}` | User has no devices with push enabled |
| `429` | — | Rate limit exceeded (100 req/sec per IP). Check `Retry-After` header |
| `503` | — | Server overloaded. Retry after a short delay |

---

## How Pushes Are Delivered to the Mobile App

When you send a push via this API, SmartAppPush delivers it to the user's devices as a **data-only FCM message**. Here's what happens on the mobile side:

1. SmartAppPush constructs an FCM data message with `title`, `body`, `tracking_token`, and your `extra_data` (serialized as a JSON string)
2. FCM delivers the message to the device
3. The mobile app's `FirebaseMessagingService.onMessageReceived()` is called — even when the app is in the background or killed
4. The mobile app builds and displays the notification using `NotificationCompat.Builder`
5. When the user taps the notification, the app extracts `tracking_token` and `extra_data` from the Intent extras

### FCM Payload Structure (what the mobile app receives)

```json
{
  "data": {
    "title": "Your order has shipped!",
    "body": "Order #4521 is on its way.",
    "tracking_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "extra_data": "{\"order_id\":\"4521\",\"screen\":\"order_detail\",\"tracking_url\":\"https://track.example.com/4521\"}"
  }
}
```

Key points for your `extra_data` design:
- `extra_data` values are serialized as a **JSON string** in the FCM data map — the mobile app must parse it
- Keep values simple and serializable (strings, numbers, booleans)
- SmartAppPush automatically injects a `tracking_token` UUID for open tracking — you do not need to include it
- Use consistent key names (e.g., `screen`, `order_id`) so the mobile app's deep link router can handle them predictably

---

## Use Case Examples

### Transactional: Order Shipped

```json
{
  "app_user_id": "user_456",
  "title": "Your order has shipped!",
  "body": "Order #7890 is on its way.",
  "extra_data": {
    "screen": "order_tracking",
    "order_id": "7890"
  },
  "dedupe_key": "order-shipped-7890"
}
```

### Real-time: Chat Message

```json
{
  "app_user_id": "user_789",
  "title": "New message from Sarah",
  "body": "Hey, are you free for lunch tomorrow?",
  "extra_data": {
    "screen": "chat",
    "chat_id": "conv_123"
  },
  "priority": 9
}
```

### Scheduled: Appointment Reminder

```json
{
  "app_user_id": "user_321",
  "title": "Appointment tomorrow",
  "body": "Your appointment with Dr. Smith is at 10:00 AM.",
  "extra_data": {
    "screen": "appointments",
    "appointment_id": "apt_555"
  },
  "schedule_at": "2026-03-30T09:00:00Z",
  "dedupe_key": "reminder-apt-555"
}
```

---

## Do / Don't Rules

### Do

- **Store `server_key` in environment variables** or a secrets manager. Do not hardcode it.
- **Use `dedupe_key`** for idempotency. If your server retries a request (e.g., after timeout), the same `dedupe_key` within 10 minutes prevents duplicate pushes.
- **Handle 404 gracefully.** It means the user has no devices with push permission enabled — this is normal, not an error in your code.
- **Respect rate limits.** 100 requests/second per IP. If you get `429`, check the `Retry-After` header and wait.
- **Retry on `503`.** The server is temporarily overloaded. Wait a short delay and retry.
- **Use `extra_data` for deep linking.** Include screen names, entity IDs, or URLs so the mobile app can navigate the user to the right place when they tap the notification.
- **Match `app_user_id` exactly.** The value must be identical to what the mobile app sends in events. If the app sends `"usr_42"`, use `"usr_42"` here — not `"user_42"` or `42`.
- **Keep `extra_data` values simple.** Since FCM delivers `extra_data` as a serialized JSON string, use strings, numbers, and booleans — avoid deeply nested objects.

### Don't

- **Do not expose `server_key` in client-side code**, mobile apps, frontend bundles, or public repositories.
- **Don't use the Developer API for bulk sends.** For campaigns targeting thousands of users, use SmartAppPush Campaigns via the dashboard. The Developer API is for targeted, individual pushes.
- **Don't ignore `push_count` in the response.** If it's 0, the push was sent to no devices (unlikely but possible in race conditions). If it's > 1, the user has multiple devices.
- **Don't retry on `401`.** Fix your `server_key` configuration — retrying won't help.

---

## Rate Limits & Retry Strategy

| Status | Action |
|--------|--------|
| `200` / `201` | Success. No retry needed |
| `400` | Client error. Fix request, don't retry same payload |
| `401` | Auth error. Fix `server_key` config, don't retry |
| `404` | User has no push-enabled devices. Handle gracefully, don't retry |
| `429` | Rate limited. Wait for `Retry-After` seconds, then retry |
| `503` | Server overloaded. Wait 1-5 seconds, then retry with exponential backoff |
| Network error | Retry with `dedupe_key` to prevent duplicates |

Recommended: exponential backoff with jitter, max 3 retries for `503` and network errors.
