Integrate Elaan in an afternoon
One API for an in-app inbox, email, and push. Drop a component into your frontend, trigger events from your backend, and Elaan handles rendering, delivery, and real-time updates across every channel.
On this page
Overview Quickstart SDK packages Authentication Trigger a notification Sync contacts Self-hostingOverview
Elaan is real-time notification infrastructure. You model notification types and templates once, then trigger a single event whenever something happens — Elaan fans it out to each channel the recipient has enabled (in-app inbox, email, push), rendering per-brand templates and delivering asynchronously.
Integrating takes three pieces, and this guide covers all of them:
- Your frontend drops in a prebuilt inbox, bell, and preferences UI — or uses the headless hooks/stores to build your own.
- Your backend mints short-lived contact tokens for the browser and triggers notification events.
- Elaan resolves channels, renders templates, and delivers — pushing live updates to connected clients.
How it works
- Your backend mints a contact token. Call
POST /v1/contacts/tokenswith your service key and a contact'sexternal_id. You get back a short-lived token scoped to that one contact. - Your frontend renders the UI. Hand the SDK a
tokenProviderthat fetches that token from your own endpoint. Drop in<NotificationBell />,<NotificationFeed />, and<Preferences />. - Your backend triggers events. When something happens, call
POST /v1/notificationswith a type key, the recipient'sexternal_id, and any template variables. Elaan accepts it (202) and delivers asynchronously. - Elaan delivers & streams. The inbox updates in real time over SSE (with polling fallback); email and push are sent by background workers.
Prerequisites
- An Elaan account — sign up at console.elaan.io. The first sign-up provisions your tenant and a default brand.
- A service key (
sk_…) from the console — used by your backend to authenticate. Keep it server-side. - At least one notification type and a template for the channels you want to send, created in the console.
The API base is https://api.elaan.io/v1 (self-hosted installs use your own host — see Self-hosting).
Quickstart
A complete React integration. Other frameworks follow the same shape — see SDK packages.
1 · Installnpm install @elaanio/react2 · Expose a token endpoint on your backend
// Your server. The service key stays here — never in the browser. app.get("/api/elaan-token", async (req, res) => { const r = await fetch("https://api.elaan.io/v1/contacts/tokens", { method: "POST", headers: { "Authorization": `Bearer ${process.env.ELAAN_SERVICE_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ external_id: req.user.id }), // your own user id }); res.json(await r.json()); // { token, contact_id, expires_in } });3 · Wrap your app & drop in components
import { ElaanProvider, NotificationBell, Preferences } from "@elaanio/react"; import "@elaanio/react/styles.css"; async function tokenProvider() { const res = await fetch("/api/elaan-token"); const { token, contact_id } = await res.json(); return { token, contactId: contact_id }; } export function App() { return ( <ElaanProvider apiBase="https://api.elaan.io/v1" tokenProvider={tokenProvider}> <NotificationBell /> <Preferences /> </ElaanProvider> ); }4 · Trigger a notification from your backend
curl https://api.elaan.io/v1/notifications \ -H "Authorization: Bearer $ELAAN_SERVICE_KEY" \ -H "Content-Type: application/json" \ -d '{ "notification_type_key": "order_shipped", "external_id": "crm-12345", "variables": { "order_id": "A-1042" } }'
That's it — the bell badge updates live, the inbox fills in, and any enabled email/push channels are delivered by background workers.
SDK packages
Every package is a thin layer over a shared, framework-agnostic core, so behavior is identical across frameworks. Pick the one for your stack:
| Package | For | Ships |
|---|---|---|
@elaanio/react | React (web) | Components + hooks, SSE realtime |
@elaanio/vue | Vue 3 | Components + composables, SSE realtime |
@elaanio/svelte | Svelte | Reactive stores (headless) |
@elaanio/react-native | React Native | Native components, SSE via react-native-sse |
@elaanio/elements | Any page / framework | Web Components (custom elements) |
@elaanio/react-core | React (custom UI) | Provider + hooks, no components |
@elaanio/core | Any JS | Client + observable stores + realtime |
All three components are available everywhere: a bell with an unread badge and popover, an inbox feed, and a preferences matrix. Auth (the tokenProvider), realtime, polling, and optimistic updates work the same in each.
React
npm install @elaanio/react
import { ElaanProvider, NotificationBell, NotificationFeed, Preferences } from "@elaanio/react"; import "@elaanio/react/styles.css"; <ElaanProvider apiBase="https://api.elaan.io/v1" tokenProvider={tokenProvider}> <NotificationBell /> {/* bell + badge + popover */} <NotificationFeed /> {/* inbox list, inline */} <Preferences /> {/* per-type × channel toggles */} </ElaanProvider>
Realtime is on by default over SSE with automatic polling fallback. Pass realtime={false} to force polling, or pollInterval={ms} to tune it.
Vue
npm install @elaanio/vue
<script setup> import { ElaanProvider, NotificationBell, Preferences } from "@elaanio/vue"; import "@elaanio/vue/styles.css"; </script> <template> <ElaanProvider apiBase="https://api.elaan.io/v1" :tokenProvider="tokenProvider"> <NotificationBell /> <Preferences /> </ElaanProvider> </template>
Composables (useNotifications, useUnreadCount, usePreferences, usePush) return Vue refs for building your own UI.
Svelte
The Svelte package is headless — it gives you native stores and you render your own markup.
npm install @elaanio/svelte
// elaan.js import { createElaan } from "@elaanio/svelte"; export const elaan = createElaan({ apiBase: "https://api.elaan.io/v1", tokenProvider, });
<script> import { elaan } from "./elaan.js"; const { state, unreadCount, markRead } = elaan.inbox; </script> <span>{$unreadCount}</span> {#each $state.notifications as n (n.id)} <button on:click={() => markRead(n.id)}>{n.title}</button> {/each}
React Native
npm install @elaanio/react-native
import { ElaanProvider, NotificationBell, Preferences } from "@elaanio/react-native"; <ElaanProvider apiBase="https://api.elaan.io/v1" tokenProvider={tokenProvider}> <NotificationBell /> <Preferences /> </ElaanProvider>
Components render with native primitives (no stylesheet import). Realtime uses SSE via react-native-sse with polling fallback. Pair with push notifications for delivery while the app is backgrounded.
Web Components
Framework-agnostic custom elements — drop them into plain HTML, Angular, Rails/Laravel views, or anywhere.
npm install @elaanio/elements
import { defineElaanElements, configureElaan } from "@elaanio/elements"; defineElaanElements(); configureElaan({ apiBase: "https://api.elaan.io/v1", tokenProvider });
<elaan-bell></elaan-bell> <elaan-feed></elaan-feed> <elaan-preferences></elaan-preferences>
Headless — bring your own UI
Every packaged component is built on hooks/stores you can use directly. For a framework we don't ship yet, drop to @elaanio/core — the same observable stores every binding is built on:
import { ElaanClient, createInboxStore } from "@elaanio/core"; const client = new ElaanClient("https://api.elaan.io/v1", tokenProvider); const inbox = createInboxStore(client, { pollInterval: 30000 }); inbox.subscribe(() => render(inbox.getState())); // getState() / getUnreadCount() inbox.markRead(id); // + markAllRead / remove / refresh
Theming
The web components (React, Vue, Web Components) are styled entirely with CSS custom properties. Override them on :root — light/dark is automatic via prefers-color-scheme.
/* your global stylesheet */
:root {
--elaan-accent: #7c3aed;
--elaan-radius: 6px;
--elaan-bg: #fff;
--elaan-text: #1a1d23;
}
Full tokens: --elaan-accent, --elaan-accent-ink, --elaan-bg, --elaan-bg-hover, --elaan-text, --elaan-muted, --elaan-border, --elaan-danger, --elaan-radius, --elaan-shadow. For heavier customization, use the headless hooks and render your own markup.
Real-time
The inbox updates live over Server-Sent Events when your deployment has realtime enabled, and falls back to polling (default every 30s) otherwise — nothing to configure. A missed signal is never a lost notification: the client reconciles against the durable inbox over REST on reconnect.
Push tokens
Register a device's push token from the frontend (the SDK sends it under the signed-in contact):
import { usePush } from "@elaanio/react"; // or /vue, /react-native const { register, unregister } = usePush(); // provider: "fcm" | "apns" | "expo" | "onesignal" | "webpush" await register(deviceToken, "fcm", "web"); // platform: ios | android | web
Authentication
Every request carries Authorization: Bearer <token>. The tenant is derived from the verified token — never from a header you send. There are two credential kinds:
- Service key (
sk_…) — held by your backend. Authorizes management calls, triggering events, and minting contact tokens. - Contact token — short-lived, scoped to a single contact. Minted by your backend and handed to the browser; the SDK uses it for the inbox, preferences, push tokens, and the stream.
Contact tokens
Your backend exchanges a contact's external_id (your own user id) for a short-lived token plus Elaan's internal contact id.
POST /v1/contacts/tokens Authorization: Bearer sk_… { "external_id": "crm-12345" }Response
{
"token": "eyJ…",
"token_type": "bearer",
"expires_in": 900,
"contact_id": "01J…"
}
Return token and contact_id to the browser from your own endpoint; the SDK refreshes automatically when the token expires by calling your tokenProvider again.
Trigger a notification
The single front door. Record an event for a recipient; Elaan resolves the enabled channels, renders templates, and delivers asynchronously. Returns 202 immediately with an event_id.
POST /v1/notifications Authorization: Bearer sk_… { "notification_type_key": "order_shipped", "external_id": "crm-12345", "branding_key": "acme", // optional; falls back to the tenant default "variables": { "order_id": "A-1042" } }Response · 202 Accepted
{ "event_id": "01J…" }
- notification_type_key — the type you created in the console.
- external_id — the recipient (your own user id).
- branding_key — optional; an unknown or omitted brand falls back to the tenant default.
- variables — a flat string map merged into the template alongside brand values and contact attributes.
Sync contacts
Create or update the contacts you'll notify. Address them by your own external_id — you never need to store Elaan's internal ids.
POST /v1/contacts Authorization: Bearer sk_… { "external_id": "crm-12345", "emails": ["ada@example.com"], "attributes": { "first_name": "Ada" } }
Every management route also accepts ext:<external_id> in place of the internal id — e.g. GET /v1/contacts/ext:crm-12345 — so your backend operates entirely in your own identifiers.
Preferences
The <Preferences /> component renders and edits the full matrix automatically. To build your own, read the matrix (every notification type × channel, with the effective on/off and whether the contact overrode it):
GET /v1/contacts/{contact_id}/preferences // contact or service token
PUT /v1/contacts/{contact_id}/preferences // { notification_type_key, channel, enabled }
In the SDK this is the usePreferences() hook / elaan.preferences store: setPreference(typeKey, channel, enabled) and clearPreference(typeKey, channel).
Delivery status
Because delivery is asynchronous, you can query an event's outcome with the event_id from the trigger response:
GET /v1/notifications/{event_id}
Authorization: Bearer sk_…
// → pending | processed | failed, with any per-channel error summary
Per-channel failures are isolated: a misconfigured email template never blocks the in-app or push delivery of the same event.
Self-hosting
Elaan is self-hostable — the same image serves the hosted product and on-prem, with config flags selecting features. Self-hosted installs get real-time SSE streaming and run entirely in your own infrastructure (single tenant, your own Postgres, your own transport credentials).
Point the SDK's apiBase at your own host; everything else is identical. See the deployment guide in the repository.
Reference
- API base:
https://api.elaan.io/v1 - npm:
@elaanio/react,@elaanio/vue,@elaanio/svelte,@elaanio/react-native,@elaanio/elements,@elaanio/react-core,@elaanio/core - SDK source & issues: github.com/ThingsIDoForLove/elaan-js
- Console: console.elaan.io — create notification types, templates, brands, and service keys.