Inbox

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

Step 1: Backend

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

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');
});

Schema

FieldTypeRequiredDescription
inboxFilterobjectYesFilter conversations by brandIds or senders, or pass {} for a team-level inbox
scopesstring[]YesPermissions granted to the token

FieldTypeDescription
brandIdsstring[]Only show conversations from these brands
sendersstring[]Only show conversations from these senders (agent IDs or phone numbers in E.164 format)

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

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.

<!-- 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

FieldTypeDescription
INIT_INBOXpostMessageSend signed token and configuration to initialize the inbox
INBOX_READYeventIframe has loaded and is ready to receive configuration
INBOX_ERROReventValidation or initialization error occurred

FieldTypeRequiredDescription
type"INIT_INBOX"YesMessage type identifier
signedTokenstringYesJWT token from your backend
primaryColorstringNoPrimary color for the inbox (defaults to #16a34a)

Complete Integration

<!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>