Client-Side API
You can use the SendHub API directly from the browser or a mobile client. The user authenticates themselves (via Google or email), so you don’t need an API secret key at all. This is how the SendHub dashboard works.
How It Works
import { createClient } from '@supabase/supabase-js'
const sendhub = createClient(
'https://noddeqgxbyujhpqxezbg.supabase.co',
'sb_publishable_YVA7_HVuhhguR-OVA-LZPg_ecJF2CY2'
)
// User signs in — you never handle credentials
await sendhub.auth.signInWithOAuth({
provider: 'google',
options: { redirectTo: window.location.origin }
})
// After redirect, the session is set automatically
// Every query is scoped to this user's role and permissions
const { data } = await sendhub
.from('conversations')
.select('*, contacts(name, phone)')
.order('last_message_at', { ascending: false })
No API secret key. No secrets. The user’s own session is the auth. RLS enforces what they can see based on their role — an agent only sees assigned businesses, an admin sees everything in the org.
Real-Time Subscriptions
Client-side is where real-time really shines — push new messages to the UI as they arrive:
sendhub
.channel('inbox')
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'messages',
}, (payload) => {
// RLS filters automatically — user only gets messages they can access
addMessageToUI(payload.new)
})
.subscribe()
Send a Message
await sendhub.from('messages').insert({
conversation_id: convoId,
business_id: workspaceId,
direction: 'outbound',
sender_type: 'agent',
sender_id: teamMemberId, // attributed to the signed-in user
content: 'Your order has shipped!'
})
Client-Side vs Server-Side API
| Client-Side (User Session) | Server-Side (API Secret Key) | |
|---|---|---|
| Auth | User signs in via Google/email — no secrets in your code | API secret key stored in your backend |
| Permissions | Automatic per-user ACL — agents see only their businesses, admins see all | Full org access regardless of who triggers the action |
| Attribution | Actions are automatically tied to the signed-in user | All actions attributed to “API” (unless you set sender_id manually) |
| Real-time | Native — subscribe to changes and push to UI | Possible, but typically polled from a backend |
| Secrets | None — safe to ship in a browser or mobile app | API secret key must be kept secret on your server |
| Session management | Handled by Supabase — auto-refresh, expiry, logout | Long-lived key (1 year), no session to manage |
| Best for | Dashboards, internal tools, customer portals, mobile apps | Automations, cron jobs, webhooks, CI/CD, MCP |
When to use client-side
- You’re building a dashboard or internal tool where team members sign in
- You want per-user permissions enforced automatically by the database
- You need real-time updates pushed to the browser
- You don’t want to manage secret keys in frontend code
When to use server-side
- Automations that run without a user present (cron, webhooks, queue workers)
- Integrations with external systems (Shopify, CRM, helpdesk)
- MCP server or CLI tools where there’s no browser
- You need org-wide access that isn’t tied to a specific user’s role
Using both
Many setups use both. For example: a React dashboard with client-side auth for the UI, plus a backend worker with an API secret key for automated replies and webhook processing. Both hit the same database, same RLS, same tables.