Self-hosted, real-time in-app notifications — free, open source, and Flutter-first.
OpenNotify lets you add real-time notifications to your Flutter app without relying on paid services like OneSignal or Firebase Cloud Messaging. Run your own notification server in minutes, and connect your Flutter app with a simple SDK.
- 🔴 Real-time delivery via WebSockets (Socket.io) — notifications arrive instantly, no polling
- 🗄️ PostgreSQL storage — every notification is saved and queryable
- 🟢 Online presence tracking via Redis (Upstash) — know which users are currently connected
- 📱 Flutter SDK —
opennotify_flutterpackage for instant integration - 🐳 Docker-ready — self-host in one command
- 🆓 Free to run — built entirely on free tiers (Supabase + Upstash)
Flutter App ──▶ OpenNotify Server (Node.js + Express + Socket.io)
│
┌─────────┴─────────┐
▼ ▼
PostgreSQL Redis (Upstash)
(Supabase) (presence)
git clone https://github.com/Abdul20009/opennotify-server.git
cd opennotify-serverIn your Supabase project, open the SQL Editor and run:
CREATE TABLE apps (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
name TEXT NOT NULL,
api_key TEXT UNIQUE NOT NULL DEFAULT gen_random_uuid()::text,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE notifications (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
app_id TEXT NOT NULL REFERENCES apps(id),
user_id TEXT NOT NULL,
title TEXT NOT NULL,
body TEXT NOT NULL,
data JSONB,
read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_notifications_user_id ON notifications(user_id);
CREATE INDEX idx_notifications_app_id ON notifications(app_id);Note: Use the Session Pooler connection string from Supabase (Settings → Database) if you're on an IPv4-only network — the direct connection requires IPv6.
Copy .env.example to .env and fill in your credentials:
cp .env.example .envDATABASE_URL="postgresql://postgres.xxxx:yourpassword@aws-x-xx-xxxx-x.pooler.supabase.com:5432/postgres?sslmode=require"
PORT=3000
UPSTASH_REDIS_REST_URL="https://your-redis-url.upstash.io"
UPSTASH_REDIS_REST_TOKEN="your-token"docker compose up --buildYour server is now running at http://localhost:3000.
Run this in Supabase SQL Editor to get an App ID:
INSERT INTO apps (name) VALUES ('My App') RETURNING *;Copy the returned id — you'll use this as appId when sending notifications.
POST /notifications
Content-Type: application/json
{
"appId": "your-app-id",
"userId": "user_123",
"title": "New message",
"body": "You have a new message from Sarah",
"data": { "screen": "chat", "chatId": "abc123" }
}If user_123 is currently connected, the notification is delivered instantly via WebSocket. It's also saved to the database either way.
GET /notifications/:userIdPATCH /notifications/:id/readGET /presence/:userIdReturns:
{ "userId": "user_123", "online": true }Add to your pubspec.yaml:
dependencies:
opennotify_flutter:
git:
url: https://github.com/Abdul20009/opennotify_flutter.gitUsage:
import 'package:opennotify_flutter/opennotify_flutter.dart';
final client = OpenNotifyClient(
serverUrl: 'http://your-server-url.com',
userId: 'user_123',
);
client.onNotification((notification) {
print('${notification.title}: ${notification.body}');
});
client.connect();Android: Add
android:usesCleartextTraffic="true"to yourAndroidManifest.xml<application>tag if your server uses plain HTTP, and ensureandroid.permission.INTERNETis declared.
| OpenNotify | Firebase/OneSignal | |
|---|---|---|
| Cost | Free, self-hosted | Free tier limits, paid scaling |
| Data ownership | Yours | Third-party |
| Flutter SDK | First-class | Generic/limited |
| Customization | Full source access | Limited |
- Backend: Node.js, Express, Socket.io
- Database: PostgreSQL (via Supabase or any Postgres instance)
- Cache/Presence: Redis (via Upstash or any Redis instance)
- Mobile SDK: Flutter / Dart
This project is open source and contributions are welcome — issues, PRs, and feature suggestions all appreciated.
MIT