> ## Documentation Index
> Fetch the complete documentation index at: https://archie.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending notifications

> Emit a notification to specific users or a whole audience with a single emitNotification mutation. Archie resolves recipients, checks preferences, and delivers on each channel.

To send a notification, your application calls a single mutation — `emitNotification` — with the **event key** of a notification type and a **target** (who should receive it). Archie does the rest: it resolves the recipients, checks their preferences, renders the templates, and delivers on each channel.

You describe *intent* ("notify these users that their build failed"); you never build per-recipient rows yourself.

**Endpoint**

```
https://archie-core.services.archie.com/graphql
```

**Required headers**

| Header | Value |
| - | - |
| `X-Project-Id` | Your project id |
| `X-Environment` | The target environment (e.g., `master`, `staging`, `production`) |
| `Authorization` | `Bearer <accessToken>` |

## The input at a glance

| Field | Required | Description |
| - | - | - |
| `eventKey` | Yes | The notification type to send (must exist and be active in this environment). |
| `target` | Yes | **Exactly one** of `userIds` or `audience` (see below). |
| `sourceService` | Yes | A short label identifying who is sending (e.g., `"billing"`, `"ci"`). Used for auditing. |
| `params` | No | A JSON object of variables for the templates (e.g., `{ "buildId": "42" }`). |
| `channels` | No | Override the type's channels for this send. In v1 the override may only contain `email` and/or `in_app`. Omit it to use each recipient's resolved channels. |
| `action` | No | An optional call-to-action with `url` and/or `text` (e.g., a "View build" link). |
| `dedupKey` | No | A key that makes the send idempotent — repeating the same `dedupKey` will not deliver twice. |
| `expiresIn` | No | How long the in-app item lives, as a duration string (e.g., `"720h"`). Clamped to a maximum of 365 days. |
| `broadcastConfirm` | No | Set to `true` to confirm an intentionally large broadcast (see *Large broadcasts* below). |

## Choosing a target

A target is **exactly one** of:

* **`userIds`** — an explicit list of user ids. Use this for transactional notifications ("notify *this* user").
* **`audience`** — a named group:
  * `SUBSCRIBERS` — everyone whose preference for this type is enabled (respecting per-user opt-in).
  * `ALL_PROJECT_USERS` — every user in the project (use sparingly; respects mandatory and anomaly rules).

## Examples

### Notify specific users

```graphql theme={null}
mutation NotifyUser {
  emitNotification(
    input: {
      eventKey: "order_shipped"
      sourceService: "fulfillment"
      target: { userIds: ["2685ec12-a4c7-491d-a155-d0b09190993b"] }
      params: { orderId: "A-1029", eta: "Friday" }
      action: { text: "Track package", url: "https://app.example.com/orders/A-1029" }
    }
  ) {
    accepted
    targetingMode
    recipientCount
  }
}
```

**Response**

```json theme={null}
{
  "data": {
    "emitNotification": {
      "accepted": true,
      "targetingMode": "user_ids",
      "recipientCount": 1
    }
  }
}
```

### Notify everyone subscribed to a type

```graphql theme={null}
mutation NotifySubscribers {
  emitNotification(
    input: {
      eventKey: "platform_updates"
      sourceService: "product"
      target: { audience: SUBSCRIBERS }
      params: { version: "2.4.0" }
      channels: [in_app, email]
    }
  ) {
    accepted
    targetingMode
    fanoutJobId
  }
}
```

For an audience send, delivery happens **asynchronously**: `accepted: true` means the request was queued. `recipientCount` is therefore `null` and a `fanoutJobId` is returned that identifies the broadcast.

### Make a send idempotent

If your service might retry, pass a stable `dedupKey`. The same key is never delivered twice:

```graphql theme={null}
mutation NotifyOnce {
  emitNotification(
    input: {
      eventKey: "invoice_payment"
      sourceService: "billing"
      target: { userIds: ["2685ec12-…"] }
      params: { invoiceId: "INV-77" }
      dedupKey: "invoice-INV-77-paid"
    }
  ) {
    accepted
    recipientCount
  }
}
```

## Understanding the result

`EmitNotificationResult` tells you what happened **synchronously**:

| Field | Meaning |
| - | - |
| `accepted` | `true` if the request was accepted (delivered for explicit users, or queued for an audience). |
| `targetingMode` | `user_ids`, `subscribers`, or `all_project_users`. |
| `recipientCount` | Number of recipients for an explicit `userIds` send; `null` for an audience (resolved asynchronously). |
| `fanoutJobId` | Identifier for an audience broadcast. |
| `rejectedReason` | When `accepted` is `false`, a short reason code explaining why. |

### When a send is rejected

If `accepted` is `false`, check `rejectedReason`. Common reasons:

| `rejectedReason` | What to do |
| - | - |
| `invalid_input` | A required field is missing or invalid, or the explicit `userIds` list is too large — split it or use an audience. |
| `target_validation_failed` | You must provide **exactly one** of `userIds` or `audience`. |
| `override_channel_not_permitted` | The `channels` override contains a channel that can't be forced in v1 (only `email` and `in_app` are allowed). |
| `broadcast_quota_exceeded` | Too many broadcasts for this type in the current window — slow down or batch. |

<Note>
  A rejection is a normal, expected response (the mutation still returns `200`); it is not a server error.
</Note>

## Large broadcasts

To protect your users from accidental mass-sends, an audience broadcast that resolves to an unusually large number of recipients is **held** unless you explicitly confirm it. If you *intend* to reach a very large audience, set `broadcastConfirm: true`:

```graphql theme={null}
mutation BigAnnouncement {
  emitNotification(
    input: {
      eventKey: "platform_updates"
      sourceService: "product"
      target: { audience: ALL_PROJECT_USERS }
      params: { headline: "New dashboard is live" }
      broadcastConfirm: true
    }
  ) {
    accepted
    fanoutJobId
    rejectedReason
  }
}
```

## Next step

See how recipients read and stream their notifications in [In-app inbox & real-time updates](/docs/features/backend/app-services/notifications/in-app-inbox).
