A self-hosted push notification service for homelab users to send notifications to iOS devices via Apple Push Notification service (APNs). Built with Go and designed for easy deployment on Ubuntu servers via Docker.
- 🔐 Multi-user support with JWT authentication and API keys
- 📱 Device management with flexible tag-based routing
- 🎯 Tag-based notifications - send to specific device groups
- 🚀 RESTful API for easy integration with automation tools
- 📊 Delivery tracking with detailed status reporting
- ⚡ Async processing with RabbitMQ for reliable delivery
- 🔄 Automatic retries with exponential backoff
- 🐳 Docker deployment with docker-compose
- 📝 Comprehensive logging and health checks
┌─────────────┐
│ iOS Client │ (Swift app)
└─────┬───────┘
│ HTTPS
│
┌─────▼─────────────────┐
│ Backend API (Go) │
│ - REST endpoints │
│ - JWT auth │
│ - Device mgmt │
└───┬───────────────────┘
│
┌───┴──┬──────┬─────────┐
│ PG │ Redis│ RabbitMQ│
└──────┴──────┴────┬────┘
│
┌───────▼───────┐
│ Worker Service│
│ - APNs sender │
│ - Retries │
└───────┬───────┘
│ HTTP/2
┌───────▼───────┐
│ Apple APNs │
└───────────────┘
- Docker and Docker Compose
- Apple Developer Account
- APNs authentication key (.p8 file)
- iOS device for testing
git clone https://github.com/JdCpuWiz/pushlab
cd pushlab
# Copy environment file and configure
cp .env.example .env
nano .env # Edit with your valuesEdit .env file:
DB_PASSWORD=your_secure_password
RABBITMQ_USER=guest
RABBITMQ_PASS=guest
REDIS_PASSWORD=your_redis_password
JWT_SECRET=your_secure_jwt_secret_at_least_32_characters_longcd docker
docker-compose up -dThis will start:
- PostgreSQL (port 5432)
- RabbitMQ (port 5672, management UI: 15672)
- Redis (port 6379)
- API server (port 8080)
- Worker service (2 replicas)
# Check health
curl http://localhost:8080/health
# View logs
docker-compose logs -f api
docker-compose logs -f workercurl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "john",
"email": "john@example.com",
"password": "securepassword123"
}'Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "uuid",
"username": "john",
"email": "john@example.com",
"api_key": "generated_api_key",
"created_at": "2024-01-01T00:00:00Z"
}
}curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "john",
"password": "securepassword123"
}'Before sending notifications, upload your APNs authentication key:
curl -X POST http://localhost:8080/api/v1/credentials/apns \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"team_id": "YOUR_TEAM_ID",
"key_id": "YOUR_KEY_ID",
"bundle_id": "com.example.app",
"environment": "production",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
}'Register an iOS device (typically done by the iOS app):
curl -X POST http://localhost:8080/api/v1/devices \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"device_name": "iPhone 15 Pro",
"device_identifier": "unique-device-id",
"device_token": "apns-device-token-from-ios",
"bundle_id": "com.example.app",
"environment": "production",
"tags": ["personal", "critical"]
}'curl -X POST http://localhost:8080/api/v1/notify \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Server Alert",
"body": "CPU usage above 90%",
"tags": ["server", "critical"],
"badge": 1,
"sound": "default",
"priority": "high",
"data": {
"server": "web-01",
"cpu": "92%"
}
}'curl -X POST http://localhost:8080/api/v1/notify \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "General Notification",
"body": "This goes to all your devices",
"sound": "default"
}'curl -X POST http://localhost:8080/api/v1/notify/device/{device_id} \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Direct Message",
"body": "This is sent to one device"
}'curl -X POST http://localhost:8080/api/v1/notify \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"title": "Automation Alert",
"body": "Backup completed successfully",
"tags": ["automation"]
}'# List notifications
curl http://localhost:8080/api/v1/notifications?limit=10 \
-H "Authorization: Bearer $JWT_TOKEN"
# Get notification details with delivery status
curl http://localhost:8080/api/v1/notifications/{notification_id} \
-H "Authorization: Bearer $JWT_TOKEN"The iOS client app (in ios-client/) provides:
- User authentication
- Automatic APNs registration
- Device token management
- Tag configuration
- Notification history
- Open
ios-client/PushLab.xcodeprojin Xcode - Configure your Team ID and Bundle ID
- Enable Push Notifications capability
- Build and run on your device
- Go to Apple Developer Portal
- Create an App ID with Push Notifications enabled
- Create an APNs Authentication Key (.p8 file)
- Note your Team ID and Key ID
- Upload the key via the API (see above)
The main configuration file is config/config.yaml:
server:
api_port: 8080
worker_count: 4
database:
host: postgres
port: 5432
user: pushlab
password: ${DB_PASSWORD}
rabbitmq:
url: amqp://guest:guest@rabbitmq:5672/
queue_name: notifications
jwt:
secret: ${JWT_SECRET}
expiry_hours: 24
apns:
default_environment: productionEnvironment variables are expanded using ${VAR} syntax.
The database schema is automatically initialized when PostgreSQL starts using the migration file in migrations/001_initial_schema.sql.
For manual migration:
docker exec -i pushlab-postgres psql -U pushlab -d pushlab < migrations/001_initial_schema.sqlcurl http://localhost:8080/healthAccess at http://localhost:15672 (default credentials: guest/guest)
# API logs
docker-compose logs -f api
# Worker logs
docker-compose logs -f worker
# All services
docker-compose logs -frest_command:
notify_pushlab:
url: http://localhost:8080/api/v1/notify
method: POST
headers:
X-API-Key: "your-api-key"
Content-Type: "application/json"
payload: >
{
"title": "{{ title }}",
"body": "{{ message }}",
"tags": ["homeassistant"]
}#!/bin/bash
API_KEY="your-api-key"
API_URL="http://localhost:8080/api/v1/notify"
send_notification() {
local title="$1"
local body="$2"
curl -X POST "$API_URL" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"title\":\"$title\",\"body\":\"$body\",\"tags\":[\"automation"]}"
}
# Usage
send_notification "Backup Complete" "Server backup finished successfully"import requests
class PushLabClient:
def __init__(self, api_key, base_url="http://localhost:8080"):
self.api_key = api_key
self.base_url = base_url
def send_notification(self, title, body, tags=None, **kwargs):
headers = {
"X-API-Key": self.api_key,
"Content-Type": "application/json"
}
payload = {
"title": title,
"body": body,
"tags": tags or []
}
payload.update(kwargs)
response = requests.post(
f"{self.base_url}/api/v1/notify",
headers=headers,
json=payload
)
return response.json()
# Usage
client = PushLabClient("your-api-key")
client.send_notification(
"Server Alert",
"CPU usage is high",
tags=["server", "critical"],
priority="high"
)Tags allow flexible notification routing:
# Device with tags: ["personal", "critical", "iphone"]
# Device with tags: ["work", "iphone"]
# Device with tags: ["personal", "ipad"]
# Send to all personal devices
curl -X POST .../notify -d '{"body":"...", "tags":["personal"]}'
# Send to critical iPhones
curl -X POST .../notify -d '{"body":"...", "tags":["critical","iphone"]}'Devices receive notifications if they have all specified tags (AND logic).
- Check worker logs:
docker-compose logs worker - Verify APNs credentials are correct
- Ensure device token is valid
- Check RabbitMQ queue depth
- Verify iOS app has notification permissions
# Check database status
docker-compose ps postgres
# Test connection
docker exec pushlab-postgres pg_isready -U pushlab# Check RabbitMQ
docker-compose ps rabbitmq
# Check queue status
curl -u guest:guest http://localhost:15672/api/queues- Change default passwords in
.envfile - Use strong JWT secret (minimum 32 characters)
- Enable SSL/TLS for production (use reverse proxy)
- Restrict network access using firewall rules
- Regular backups of PostgreSQL database
- Rotate API keys periodically
- Monitor logs for suspicious activity
For production use:
- Use a reverse proxy (nginx/Caddy) with SSL/TLS
- Set up proper firewall rules
- Enable database backups
- Use secrets management (Vault, etc.)
- Monitor resource usage
- Set up log aggregation
- Configure rate limiting
# Start dependencies only
docker-compose up -d postgres rabbitmq redis
# Run API locally
cd backend
export CONFIG_PATH=../config/config.yaml
export DB_PASSWORD=pushlab_password
export JWT_SECRET=your_secret
go run cmd/api/main.go
# Run worker locally
go run cmd/worker/main.gocd backend
go test ./...Full API documentation:
POST /api/v1/auth/register- Register new userPOST /api/v1/auth/login- LoginGET /api/v1/auth/apikey- Generate API keyPOST /api/v1/devices- Register deviceGET /api/v1/devices- List devicesPUT /api/v1/devices/{id}- Update deviceDELETE /api/v1/devices/{id}- Delete devicePOST /api/v1/notify- Send notificationPOST /api/v1/notify/device/{id}- Send to deviceGET /api/v1/notifications- List notificationsGET /api/v1/notifications/{id}- Get notification detailsPOST /api/v1/credentials/apns- Upload APNs credentialsGET /api/v1/credentials/apns- List credentialsDELETE /api/v1/credentials/apns/{id}- Delete credentialsGET /health- Health check
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
MIT License - see LICENSE file for details
- GitHub Issues: Report bugs
- Documentation: Wiki
- Web dashboard for device management
- Scheduled notifications
- Notification templates
- Rate limiting per user
- Webhook callbacks for delivery status
- Multi-channel support (email, SMS)
- Analytics dashboard
- Silent notifications support
- Notification grouping
- Localization support