> ## 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).
