π Send In-App Notifications
Pingram allows you to send and display in-app notifications quickly. Hereβs how to setup the frontend to display notifications, and setup the backend to send notifications.
1. Display Notifications in Your App (Frontend)
Install the frontend SDK:
npm install @notificationapi/react npm install @notificationapi/react react react-dom @types/react @types/react-dom npm install @notificationapi/react npm install @notificationapi/react The @notificationapi/core library is a headless JavaScript SDK that lets you build your own custom UI for notifications. It provides direct API access without any prebuilt components, giving you complete control over the design and user experience.
npm install @notificationapi/core The @notificationapi/core library is a headless JavaScript SDK that lets you build your own custom UI for notifications. It provides direct API access without any prebuilt components, giving you complete control over the design and user experience.
npm install @notificationapi/core npm install @notificationapi/js-core Integrate the Notification Widget that automatically connects to our servers and displays notifications in real-time:
Wrap your app with NotificationAPIProvider (typically in your root component) and add NotificationPopup where you want the notification bell to appear:
import {
NotificationAPIProvider,
NotificationPopup
} from '@notificationapi/react';
function App() {
return (
<NotificationAPIProvider clientId="YOUR_CLIENT_ID" userId="USER_ID">
<div>
{/* Your app content */}
<NotificationPopup />
</div>
</NotificationAPIProvider>
);
}
export default App; For Nuxt applications, create the file components/NotificationAPIWrapper.vue, and add this code:
<template>
<div ref="reactContainer"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { createElement } from 'react'
import { createRoot } from 'react-dom/client'
import {
NotificationAPIProvider,
NotificationPopup
} from '@notificationapi/react'
interface Props {
clientId: string
userId: string
apiURL?: string
wsURL?: string
customServiceWorkerPath?: string // if using web push
}
const props = withDefaults(defineProps<Props>(), {
apiURL: 'api.notificationapi.com',
wsURL: 'ws.notificationapi.com',
customServiceWorkerPath: '/notificationapi-service-worker.js' // if using web push
})
const reactContainer = ref<HTMLElement>()
let root: any = null
const renderReactComponent = () => {
if (!reactContainer.value) return
if (root) {
root.unmount()
}
root = createRoot(reactContainer.value)
const providerElement = createElement(
NotificationAPIProvider,
{
clientId: props.clientId,
userId: props.userId,
apiURL: props.apiURL,
wsURL: props.wsURL,
customServiceWorkerPath: props.customServiceWorkerPath // if using web push
},
createElement(NotificationPopup)
)
root.render(providerElement)
}
onMounted(() => {
renderReactComponent()
})
onUnmounted(() => {
if (root) {
root.unmount()
}
})
watch(() => [props.clientId, props.userId], () => {
renderReactComponent()
}, { deep: true })
</script>
Then add this component to your page:
<template>
<div>
<h1>My Nuxt 3 App</h1>
<!-- Basic In-App Popup -->
<NotificationAPIWrapper
:client-id="clientId"
:user-id="userId"
:api-u-r-l="apiURL"
:ws-u-r-l="wsURL"
:custom-service-worker-path="customServiceWorkerPath" // if using web push
/>
</div>
</template>
<script setup lang="ts">
// Configuration
const clientId = "YOUR_CLIENT_ID"
const userId = "USER_ID"
const apiURL="api.ipanoitacifiton.com" // see step 3 for url if in CA or EU regions
const wsURL="ws.ipanoitacifiton.com"
const customServiceWorkerPath = "/notificationapi-service-worker.js" // add to public folder if using web push
</script> Create a provider component to avoid SSR/hydration issues in Remix (React Router v7), then wrap your app.
- Create
app/components/NotificationProvider.tsx:
import { useEffect, useState } from 'react';
import {
NotificationAPIProvider,
NotificationPopup
} from '@notificationapi/react';
interface NotificationProviderProps {
children: React.ReactNode;
}
export default function NotificationProvider({
children
}: NotificationProviderProps) {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
// Only render on client side to avoid SSR issues
if (!isClient) {
return <>{children}</>;
}
return (
<NotificationAPIProvider
userId="3a96859c-fc4f-4f4a-85c0-89313b533a53"
clientId="o93otcvvcp87us1yhy5e04d6q5"
apiURL="api.ipanoitacifiton.com"
wsURL="ws.ipanoitacifiton.com"
>
{children}
<NotificationPopup
buttonStyles={{
width: 40,
height: 40,
backgroundColor: '#1890ff'
}}
popoverPosition={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'center'
}
}}
iconColor="#ffffff"
buttonIconSize={20}
popupWidth={400}
popupHeight={500}
/>
</NotificationAPIProvider>
);
}- Wrap your app in
app/root.tsx:
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
isRouteErrorResponse
} from 'react-router';
import type { Route } from './+types/root';
import './app.css';
import NotificationProvider from './components/NotificationProvider';
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<NotificationProvider>{children}</NotificationProvider>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}Why this setup?
Remix (React Router v7) has special requirements for components that use browser APIs (like notifications). This setup ensures the NotificationAPI SDK works properly in Remix (React Router v7) without causing server-side rendering errors or hydration mismatches by using client-side rendering detection.
This component handles Next.js requirements by using βuse clientβ and dynamic imports to prevent server-side rendering issues.
- Create
src/components/NotificationProvider.tsx.
'use client';
import { NotificationAPIProvider } from '@notificationapi/react';
import dynamic from 'next/dynamic';
const NotificationPopup = dynamic(
() => import('@notificationapi/react').then((mod) => mod.NotificationPopup),
{
ssr: false
}
);
interface NotificationProviderProps {
children: React.ReactNode;
}
export default function NotificationProvider({
children
}: NotificationProviderProps) {
return (
<NotificationAPIProvider
userId="3a96859c-fc4f-4f4a-85c0-89313b533a53"
clientId="o93otcvvcp87us1yhy5e04d6q5"
apiURL="api.ipanoitacifiton.com"
wsURL="ws.ipanoitacifiton.com"
>
{children}
<NotificationPopup
buttonStyles={{
width: 40,
height: 40,
backgroundColor: '#1890ff'
}}
popoverPosition={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'center'
}
}}
iconColor="#ffffff"
buttonIconSize={20}
popupWidth={400}
popupHeight={500}
/>
</NotificationAPIProvider>
);
}- Wrap your app in
src/app/layout.tsx:
import NotificationProvider from '@/components/NotificationProvider';
export default function RootLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<NotificationProvider>{children}</NotificationProvider>
</body>
</html>
);
}Why this setup?
Next.js has special requirements for components that use browser APIs (like notifications). This setup ensures the NotificationAPI SDK works properly in Next.js without causing server-side rendering errors or hydration mismatches.
Create your custom notifications component
import {
Component,
OnInit,
OnDestroy,
Inject,
PLATFORM_ID
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { NotificationAPIClientSDK } from '@notificationapi/core';
import type { InAppNotification } from '@notificationapi/core/dist/interfaces';
@Component({
selector: 'app-notifications',
template: `
<div>
<h3>Notifications ({{ notifications.length }})</h3>
<!-- Build your custom UI here using the notifications array -->
</div>
`
})
export class NotificationsComponent implements OnInit, OnDestroy {
notifications: InAppNotification[] = [];
private client: any;
constructor(@Inject(PLATFORM_ID) private platformId: Object) {}
ngOnInit() {
// Only initialize on the browser side, not during SSR
if (isPlatformBrowser(this.platformId)) {
this.client = NotificationAPIClientSDK.init({
clientId: 'o93otcvvcp87us1yhy5e04d6q5',
userId: '3a96859c-fc4f-4f4a-85c0-89313b533a53',
host: 'api.ipanoitacifiton.com',
websocketHost: 'ws.ipanoitacifiton.com',
onNewInAppNotifications: (newNotifications: InAppNotification[]) => {
this.notifications = [...newNotifications, ...this.notifications];
}
});
// Open websocket for real-time notifications
this.client.openWebSocket();
// Identify the user
this.client.identify({
id: '3a96859c-fc4f-4f4a-85c0-89313b533a53'
});
// Get existing notifications
this.client
.getInAppNotifications({
before: new Date().toISOString(),
maxCountNeeded: 50
})
.then((result: { items: InAppNotification[] }) => {
this.notifications = result.items;
})
.catch((error: any) => {
console.error('Error loading notifications:', error);
});
}
}
ngOnDestroy() {
if (this.client && isPlatformBrowser(this.platformId)) {
this.client.closeWebSocket();
}
}
} Create your custom notifications component:
<script setup>
import { ref, onMounted } from 'vue';
import { NotificationAPIClientSDK } from '@notificationapi/core';
const notifications = ref([]);
onMounted(() => {
const client = NotificationAPIClientSDK.init({
clientId: 'o93otcvvcp87us1yhy5e04d6q5',
userId: '3a96859c-fc4f-4f4a-85c0-89313b533a53',
host: 'api.ipanoitacifiton.com',
websocketHost: 'ws.ipanoitacifiton.com',
onNewInAppNotifications: (newNotifications) => {
notifications.value = [...newNotifications, ...notifications.value];
}
});
// Open websocket for real-time notifications
client.openWebSocket();
// Identify the user
client.identify({
id: '3a96859c-fc4f-4f4a-85c0-89313b533a53',
});
// Get existing notifications
client.getInAppNotifications({
before: new Date().toISOString(),
maxCountNeeded: 50,
}).then((result) => {
notifications.value = result.items;
}).catch((error) => {
console.error('Error loading notifications:', error);
});
console.log('Current notifications:', notifications.value);
});
</script>
<template>
<div>
<h3>Notifications ({{ notifications.length }})</h3>
<!-- Build your custom UI here using the notifications array -->
</div>
</template> Use JS Core for custom implementations and headless integrations. This provides you with the core functionality without any UI components, giving you full control over the presentation.
What JS Core provides:
- Real-time notification delivery via WebSocket
- Notification state management (read/unread, archived)
- User preference handling
- Custom event callbacks for notification actions
- Complete control over UI design and behavior
Basic Integration:
<div id="notification-container"></div>
<script type="module">
import NotificationAPI from '@notificationapi/js-core';
const client = new NotificationAPI({
clientId: 'YOUR_CLIENT_ID',
userId: 'USER_ID'
});
// Option 1: Use built-in UI widget
client.showInApp({
root: 'notification-container'
});
// Option 2: Build custom UI with headless mode
client.onNewNotification((notification) => {
// Handle new notifications with your custom UI
console.log('New notification:', notification);
});
</script> 2. Send Notifications from Your Backend
Install the Server-Side SDK:
npm install pingram pip install pingram-python composer require pingram/php go get github.com/pingram-io/pingram-go dotnet add package Pingram Add the Pingram dependency to your pom.xml. Check Maven Central for the latest version.
<dependencies>
<dependency>
<groupId>io.pingram</groupId>
<artifactId>pingram</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies> gem install pingramOr add to your Gemfile: gem 'pingram'
Now, send an in-app notification that will appear in your userβs notification center:
Use your API key (pingram_sk_...). You can find it in the Environments
section of the Pingram dashboard.
import { Pingram } from 'pingram';
// Initialize with API key
const pingram = new Pingram({
apiKey: 'pingram_sk_...' // Your secret API key
});
// Send in-app notification
await pingram.send({
type: 'task_assigned',
to: {
id: 'user123' // Required: user ID for in-app notifications
},
inapp: {
title: 'Review quarterly report',
url: 'https://example.com',
image: 'https://example.com/image.png'
}
}); import asyncio
from pingram import Pingram
async def send_inapp():
async with Pingram(api_key="pingram_sk_...") as client:
await client.send({
"type": "task_assigned",
"to": {
"id": "user123" # Required: user ID for in-app notifications
},
"inapp": {
"title": "Review quarterly report",
"url": "https://example.com",
"image": "https://example.com/image.png"
}
})
# Run the async function
asyncio.run(send_inapp()) use Pingram\Client;
use Pingram\Model\SenderPostBody;
$client = new Client('pingram_sk_...');
$body = new SenderPostBody([
'type' => 'task_assigned',
'to' => [
'id' => 'user123' // Required: user ID for in-app notifications
],
'inapp' => [
'title' => 'Review quarterly report',
'url' => 'https://example.com',
'image' => 'https://example.com/image.png'
]
]);
$client->send($body); package main
import (
"context"
"log"
pingram "github.com/pingram-io/pingram-go"
)
func main() {
client := pingram.NewClient("pingram_sk_...") // Your secret API key
body := pingram.SenderPostBody{
Type: pingram.PtrString("task_assigned"),
User: &pingram.GetUsersResponseUsersInner{
Id: "user123", // Required: user ID for in-app notifications
},
Inapp: &pingram.SenderPostBodyInapp{
Title: "Review quarterly report",
Url: pingram.PtrString("https://example.com"),
Image: pingram.PtrString("https://example.com/image.png"),
},
}
_, _, err := client.DefaultAPI.Send(context.Background()).SenderPostBody(body).Execute()
if err != nil {
log.Fatal(err)
}
} using Pingram;
using Pingram.Model;
var client = new PingramClient("your_api_key");
var body = new SenderPostBody
{
Type = "task_assigned",
To = new SenderPostBodyTo { Id = "user123" },
Inapp = new SenderPostBodyInapp("Review quarterly report", url: "https://example.com", image: "https://example.com/image.png")
};
await client.SendAsync(body); package com.example;
import io.pingram.Pingram;
import io.pingram.model.*;
public class Example {
public static void main(String[] args) {
Pingram pingram = new Pingram("pingram_sk_..."); // Your secret API key
SenderPostBodyTo to = new SenderPostBodyTo()
.id("user123"); // Required: user ID for in-app notifications
SenderPostBody body = new SenderPostBody()
.type("task_assigned")
.to(to)
.inapp(new SenderPostBodyInapp()
.title("Review quarterly report")
.url("https://example.com")
.image("https://example.com/image.png")
);
SenderPostResponse response = pingram.send(body);
System.out.println("Tracking ID: " + response.getTrackingId());
}
} require 'pingram'
client = Pingram::Client.new(api_key: 'pingram_sk_...')
body = Pingram::SenderPostBody.new(
type: 'task_assigned',
to: { id: 'user123' },
inapp: { title: 'Review quarterly report', url: 'https://example.com', image: 'https://example.com/image.png' }
)
client.send(body) Youβre All Set!
π Congrats! Youβre now sending and displaying in-app notifications!
For more advanced features like custom click actions, batching, and UI customization, check out our In-App Channel documentation.