> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.pinnacle.sh/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pinnacle.sh/_mcp/server.

# Inbox

> Embed a full-featured messaging inbox into your application

The inbox embed gives your users a complete RCS and SMS messaging experience — conversations, message history, rich media, and real-time updates.

![Send RCS Demo](https://pncl.to/5NVSekVskYnXtQOlMBOOMDGHx5E5cV)

## Step 1: Backend

Create an endpoint that requests a signed JWT from Pinnacle. Control which conversations and UI appear in the inbox.

#### server.ts

```typescript
import 'dotenv/config';
import express from 'express';

const app = express();
app.use(express.json());

app.post('/api/get-inbox-token', async (req, res) => {
  const response = await fetch('https://api.pinnacle.sh/embed/get-token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'pinnacle-api-key': process.env.PINNACLE_API_KEY!
    },
    body: JSON.stringify({
      inboxFilter: {},  // optional — pass brandIds or senders to filter, or {} for a team-level inbox
      scopes: [
        'CONVERSATIONS.LIST',       // required
        'CONVERSATIONS.MESSAGES',    // required
        'CONVERSATIONS.SEND',
        'CONVERSATIONS.CREATE',
        'UPLOAD_FILE',
        'REALTIME.CONNECTION'
      ]
    })
  });

  const { signedToken } = await response.json();
  res.json({ signedToken });
});

app.listen(8080, () => {
  console.log('Server running at http://localhost:8080');
});
```

#### package.json

```json
{
  "name": "inbox-embed",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "tsx watch server.ts",
    "start": "tsx server.ts"
  },
  "dependencies": {
    "dotenv": "^16.4.7",
    "express": "^5.2.1"
  },
  "devDependencies": {
    "@types/express": "^5.0.6",
    "@types/node": "^20",
    "tsx": "^4.19.3",
    "typescript": "^5"
  }
}
```

#### .env.example

```bash
# https://app.pinnacle.sh/dashboard/development/api-keys
PINNACLE_API_KEY=pnclk_your_api_key_here
```

### Schema

| Field         | Type       | Required | Description                                                                          |
| ------------- | ---------- | -------- | ------------------------------------------------------------------------------------ |
| `inboxFilter` | `object`   | Yes      | Filter conversations by `brandIds` or `senders`, or pass `{}` for a team-level inbox |
| `scopes`      | `string[]` | Yes      | Permissions granted to the token                                                     |

\


#### inboxFilter

| Field      | Type       | Description                                                                             |
| ---------- | ---------- | --------------------------------------------------------------------------------------- |
| `brandIds` | `string[]` | Only show conversations from these brands                                               |
| `senders`  | `string[]` | Only show conversations from these senders (agent IDs or phone numbers in E.164 format) |

> **Info**
>
> Filters are mutually exclusive — pass either `brandIds` or `senders`, not both. Pass an empty object to show all conversations for the team.

#### scopes

| Scope                    | Required | Description                                                                                                       |
| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `CONVERSATIONS.LIST`     | Yes      | List and search conversations                                                                                     |
| `CONVERSATIONS.MESSAGES` | Yes      | Read message history                                                                                              |
| `CONVERSATIONS.SEND`     | No       | Send RCS, SMS, and MMS messages.  If omitted, the message sending UI will not appear.                             |
| `CONVERSATIONS.CREATE`   | No       | Start new conversations.  If omitted, the conversation creation UI will not appear.                               |
| `UPLOAD_FILE`            | No       | Upload images, videos, and PDFs.  If omitted, the media upload UI will not appear.                                |
| `REALTIME.CONNECTION`    | No       | Receive real-time message and conversation updates.  If omitted, the realtime connection will not be initialized. |

## Step 2: Frontend

Add the iframe to your page. Subscribe to the `message` event to listen for `INBOX_READY`, then send `INIT_INBOX` with your signed token to initialize the inbox.

```html
<!-- Other page content -->

<iframe
  id="inbox"
  src="https://embed.pinnacle.sh/embed/v2/inbox"
  style="width: 100%; height: 100vh; border: none;">
</iframe>

<!-- Other page content -->

<script>
  const iframe = document.getElementById('inbox');

  window.addEventListener('message', async (event) => {
    if (event.origin !== 'https://embed.pinnacle.sh') return;

    if (event.data.type === 'INBOX_READY') {
      // Fetch token from your backend
      const res = await fetch('/api/get-inbox-token', { method: 'POST' });
      const { signedToken } = await res.json();

      // Initialize the inbox
      iframe.contentWindow.postMessage({
        type: 'INIT_INBOX',             // required
        signedToken: signedToken,       // required
        primaryColor: '#16a34a'
      }, 'https://embed.pinnacle.sh');
    }

    if (event.data.type === 'INBOX_ERROR') {
      // handle iframe init failure here
    }
  });
</script>

<!-- Other page content -->
```

### Events

| Field         | Type          | Description                                                 |
| ------------- | ------------- | ----------------------------------------------------------- |
| `INIT_INBOX`  | `postMessage` | Send signed token and configuration to initialize the inbox |
| `INBOX_READY` | `event`       | Iframe has loaded and is ready to receive configuration     |
| `INBOX_ERROR` | `event`       | Validation or initialization error occurred                 |

\


#### INIT\_INBOX

| Field          | Type           | Required | Description                                         |
| -------------- | -------------- | -------- | --------------------------------------------------- |
| `type`         | `"INIT_INBOX"` | Yes      | Message type identifier                             |
| `signedToken`  | `string`       | Yes      | JWT token from your backend                         |
| `primaryColor` | `string`       | No       | Primary color for the inbox (defaults to `#16a34a`) |

#### INBOX\_READY

Emitted by the iframe when it has loaded and is ready to receive configuration. Listen for this event before sending `INIT_INBOX`.

#### INBOX\_ERROR

Emitted by the iframe when a validation or initialization error occurs.

## Complete Integration

#### index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Inbox</title>
</head>
<body>
  <iframe
    id="inbox"
    src="https://embed.pinnacle.sh/embed/v2/inbox"
    style="width: 100%; height: 100vh; border: none;">
  </iframe>

  <script>
    const iframe = document.getElementById('inbox');

    window.addEventListener('message', async (event) => {
      if (event.origin !== 'https://embed.pinnacle.sh') return;

      if (event.data.type === 'INBOX_READY') {
        // Fetch token from your backend
        const res = await fetch('/api/get-inbox-token', { method: 'POST' });
        const { signedToken } = await res.json();

        // Initialize the inbox
        iframe.contentWindow.postMessage({
          type: 'INIT_INBOX',             // required
          signedToken: signedToken,       // required
          primaryColor: '#16a34a'
        }, 'https://embed.pinnacle.sh');
      }

      if (event.data.type === 'INBOX_ERROR') {
        // handle iframe init failure here
      }
    });
  </script>
</body>
</html>
```

#### server.ts

```typescript
import 'dotenv/config';
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
app.use(express.json());
app.use(express.static('public'));

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'index.html'));
});

app.post('/api/get-inbox-token', async (req, res) => {
  const response = await fetch('https://api.pinnacle.sh/embed/get-token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'pinnacle-api-key': process.env.PINNACLE_API_KEY!
    },
    body: JSON.stringify({
      inboxFilter: {},  // optional — pass brandIds or senders to filter, or {} for a team-level inbox
      scopes: [
        'CONVERSATIONS.LIST',       // required
        'CONVERSATIONS.MESSAGES',    // required
        'CONVERSATIONS.SEND',
        'CONVERSATIONS.CREATE',
        'UPLOAD_FILE',
        'REALTIME.CONNECTION'
      ]
    })
  });

  const { signedToken } = await response.json();
  res.json({ signedToken });
});

app.listen(8080, () => {
  console.log('Server running at http://localhost:8080');
});
```

#### package.json

```json
{
  "name": "inbox-embed",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "tsx watch server.ts",
    "start": "tsx server.ts"
  },
  "dependencies": {
    "dotenv": "^16.4.7",
    "express": "^5.2.1"
  },
  "devDependencies": {
    "@types/express": "^5.0.6",
    "@types/node": "^20",
    "tsx": "^4.19.3",
    "typescript": "^5"
  }
}
```

#### .env.example

```bash
# https://app.pinnacle.sh/dashboard/development/api-keys
PINNACLE_API_KEY=pnclk_your_api_key_here
```