Go MIT

shove

Asynchronous & persistent push notification service, supporting APNS, FCM, Web Push, Telegram and Email. Written in Go (Golang). Mirror of https://codeberg.org/pennersr/shove

P

pennersr

Dernière activité 20 août 2026
pennersr/shove

285

étoiles

25

forks

3

issues ouvertes

androidandroid-notificationsapnsfcmfirebase-cloud-messaginggcmgogolangiosios-notificationsnotificationspush-notificationstelegram-bot-apiwebpushwebpush-notifications

Ce README est souvent en anglais.

  ░▒▓███████▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓██████▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓████████▓▒░
 ░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░
 ░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒▒▓█▓▒░░▒▓█▓▒░
  ░▒▓██████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒▒▓█▓▒░░▒▓██████▓▒░
        ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▓█▓▒░ ░▒▓█▓▒░
        ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▓█▓▒░ ░▒▓█▓▒░
 ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓██████▓▒░   ░▒▓██▓▒░  ░▒▓████████▓▒░

   👊 ░▒▓ w h e n   p u s h   c o m e s   t o   s h o v e ▓▒░ 🔔

Shove

Go Report Card Written in Emacs Pipeline Status

Background

This is the replacement for Pulsus which has been steadily serving up to 100M push notifications. But, given that it was still using the binary APNS protocol it was due for an upgrade.

Overview

Design:

  • Asynchronous: a push client can just fire & forget.
  • Multiple workers per push service.
  • Less moving parts: when using Redis, you can push directly to the queue, bypassing the need for the Shove server to be up and running.

Supported push services:

  • APNS
  • Email: supports automatic creation of email digests in case the rate limit is exceeded
  • FCM
  • Telegram: supports squashing multiple messages into one in case the rate limit is exceeded
  • Webhook: issue arbitrary webhook posts
  • Web Push

Features:

  • Feedback: asynchronously receive information on invalid device tokens.
  • Queueing: both in-memory and persistent via Redis.
  • Exponential back-off in case of failure.
  • Prometheus support.
  • Squashing of messages in case rate limits are exceeded.

Why?

Usage

Running

Usage:

$ shove -h
Usage of ./shove:
  -api-addr string
        API address to listen to (default ":8322")
  -apns-key-file string
        APNS token authentication key file (.p8)
  -apns-key-id string
        APNS Key ID (from the Apple Developer account)
  -apns-sandbox-key-file string
        APNS sandbox token authentication key file (.p8)
  -apns-sandbox-key-id string
        APNS sandbox Key ID (from the Apple Developer account)
  -apns-team-id string
        APNS Team ID (from the Apple Developer account)
  -apns-certificate-file string
        APNS certificate file
  -apns-sandbox-certificate-file string
        APNS sandbox certificate file
  -apns-workers int
        The number of workers pushing APNS messages (default 4)
  -email-host string
        Email host
  -email-port int
        Email port (default 25)
  -email-rate-amount int
        Email max. rate (amount)
  -email-rate-per int
        Email max. rate (per seconds)
  -email-tls
        Use TLS
  -email-tls-insecure
        Skip TLS verification
  -fcm-credentials-file string
        Path to FCM service account JSON file
  -fcm-workers int
        The number of workers pushing FCM messages (default 4)
  -queue-redis string
        Use Redis queue (Redis URL)
  -telegram-bot-token string
        Telegram bot token
  -telegram-rate-amount int
        Telegram max. rate (amount)
  -telegram-rate-per int
        Telegram max. rate (per seconds)
  -telegram-workers int
        The number of workers pushing Telegram messages (default 2)
  -webhook-workers int
        The number of workers pushing Webhook messages
  -webpush-vapid-private-key string
        VAPID public key
  -webpush-vapid-public-key string
        VAPID public key
  -webpush-vapid-keys-file string
        VAPID keys file path
  -webpush-workers int
        The number of workers pushing Web messages (default 8)

Start the server:

$ shove \
    -api-addr localhost:8322 \
    -queue-redis redis://redis:6379 \
    -fcm-credentials-file /etc/shove/fcm/credentials.json \
    -apns-certificate-file /etc/shove/apns/production/bundle.pem -apns-sandbox-certificate-file /etc/shove/apns/sandbox/bundle.pem \
    -webpush-vapid-keys-file=/etc/shove/webpush/vapid-keys.json \
    -telegram-bot-token $TELEGRAM_BOT_TOKEN

APNS

Two authentication methods are supported: token-based (a .p8 auth key) and certificate-based (a .pem certificate). Certificate and token authentication are mutually exclusive per environment.

Token-based authentication

To obtain a token auth key: in the Apple Developer portal go to Certificates, Identifiers & Profiles → Keys, create a key with Apple Push Notifications service (APNs) enabled, and download the .p8 (it can only be downloaded once). Note the Key ID shown for the key and your Team ID from the Membership page.

Each environment is enabled by supplying its key file. A key configured for Sandbox & Production (the default, and all pre-2024 keys) works for both, so the same file and Key ID are used for each:

$ shove \
    -apns-key-file /etc/shove/apns/AuthKey_ABCD1234.p8 \
    -apns-key-id ABCD1234 \
    -apns-sandbox-key-file /etc/shove/apns/AuthKey_ABCD1234.p8 \
    -apns-sandbox-key-id ABCD1234 \
    -apns-team-id XYZ1234567

If you created environment-scoped keys, point each flag at its own .p8 / Key ID.

Certificate-based authentication

Certificate-based authentication uses a .pem certificate per environment, via -apns-certificate-file / -apns-sandbox-certificate-file.

Pushing

Push an APNS notification:

$ curl  -i  --data '{"service": "apns", "headers": {"apns-priority": 10, "apns-topic": "com.shove.app"}, "payload": {"aps": { "alert": "hi"}}, "token": "81b8ecff8cb6d22154404d43b9aeaaf6219dfbef2abb2fe313f3725f4505cb47"}' http://localhost:8322/api/push/apns

A successful push results in:

HTTP/1.1 202 Accepted
Date: Tue, 07 May 2019 19:00:15 GMT
Content-Length: 2
Content-Type: text/plain; charset=utf-8

OK

FCM

Push an FCM notification:

$ curl  -i  --data '{"message": {"notification": {"body": "Hello world!", "title": "Test"}, "token": "c7VmdNNHQaGTLkmi....15CmMs"}}' http://localhost:8322/api/push/fcm

Webhook

Push a Webhook call, containing arbitrary body content:

$ curl  -i  --data '{"url": "http://localhost:8000/api/webhook", "headers": {"foo": "bar"}, "body": "Hello world!"}' http://localhost:8322/api/push/webhook

Or, post JSON:

$ curl  -i  --data '{"url": "http://localhost:8000/api/webhook", "headers": {"foo": "bar"}, "data": {"hello": "world!"}}' http://localhost:8322/api/push/webhook

WebPush

Push a WebPush notification:

$ curl  -i  --data '{"subscription": {"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/gAAAAAc4BA....UrjGlg","keys":{"auth":"Hbj3ap...al9ew","p256dh":"BeKdTC3...KLGBJlgF"}}, "headers": {"ttl": 3600, "urgency": "high"}, "token": "use-this-for-feedback-instead-of-subscription", "payload": {"hello":"world"}}' http://localhost:8322/api/push/webpush

The subscription (serialized as a JSON string) is used for receiving feedback. Alternatively, you can specify an optional token parameter as done in the example above.

Telegram

Push a Telegram notification:

$ curl  -i  --data '{"method": "sendMessage", "payload": {"chat_id": "12345678", "text": "Hello!"}}' http://localhost:8322/api/push/telegram

Note that the Telegram Bot API documents chat_id as "Integer or String" -- Shove requires strings to be passed. For users that disconnected from your bot the chat ID will be communicated back through the feedback mechanism. Here, the token will equal the unreachable chat ID.

Receive Feedback

Outdated/invalid tokens are communicated back. To receive those, you can periodically query the feedback channel to receive token feedback, and remove those from your database:

$ curl -X POST 'http://localhost:8322/api/feedback'

{
  "feedback": [
    {"service":"apns-sandbox",
     "token":"881becff86cbd221544044d3b9aeaaf6314dfbef2abb2fe313f3725f4505cb47",
     "reason":"invalid"}
  ]
}

Email

In order to keep your SMTP server safe from being blacklisted, the email service supports rate limitting. When the rate is exceeded, multiple mails are automatically digested.

$ shove \
    -email-host localhost \
    -email-port 1025 \
    -api-addr localhost:8322 \
    -email-rate-amount 3 \
    -email-rate-per 10 \
    -queue-redis redis://localhost:6379

Push an email:

$ curl -i -X POST --data @./scripts/email.json http://localhost:8322/api/push/email

If you send too many emails, you'll notice that they are digested, and at a later time, one digest mail is being sent:

2021/03/23 21:15:57 Using Redis queue at redis://localhost:6379
2021/03/23 21:15:57 Initializing Email service
2021/03/23 21:15:57 Serving on localhost:8322
2021/03/23 21:15:57 Shove server started
2021/03/23 21:15:57 email: Worker started
2021/03/23 21:15:57 email: Digester started
2021/03/23 21:15:58 email: Sending email
2021/03/23 21:15:59 email: Sending email
2021/03/23 21:15:59 email: Sending email
2021/03/23 21:16:00 email: Rate to john@doe.org exceeded, email digested
2021/03/23 21:16:12 email: Rate to john@doe.org exceeded, email digested
2021/03/23 21:16:18 email: Sending digest email

Redis Queues

Shove is being used to push a high volume of notifications in a production environment, consisting of various microservices interacting together. In such a scenario, it is important that the various services are not too tightly coupled to one another. For that purpose, Shove offers the ability to post notifications directly to a Redis queue.

Posting directly to the Redis queue, instead of using the HTTP service endpoints, has the advantage that you can take Shove offline without disturbing the operation of the clients pushing the notifications.

Shove intentionally tries to make as little assumptions on the notification payloads being pushed, as they are mostly handed over as is to the upstream services. So, when using Shove this way, the client is responsible for handing over a raw payload. Here's an example:

package main

import (
	"encoding/json"
	"codeberg.org/pennersr/shove/pkg/shove"
	"log"
	"os"
)

type FCMNotification struct {
	To       string            `json:"to"`
	Data     map[string]string `json:"data,omitempty"`
}

func main() {
	redisURL := os.Getenv("REDIS_URL")
	if redisURL == "" {
		redis_URL = "redis://localhost:6379"
	}
	client := shove.NewRedisClient(redisURL)

	notification := FCMNotification{
		To:   "token....",
		Data: map[string]string{},
	}

	raw, err := json.Marshal(notification)
	if err != nil {
		log.Fatal(err)
	}
	err = client.PushRaw("fcm", raw)
	if err != nil {
		log.Fatal(err)
	}
}

Status

Used in production, over at:

Projets similaires

A push notification server written in Go (Golang).

Goandroidapnsgcm
Aappleboy
8,8 k étoiles887

Push notification server for APNs, FCM and Web Push, written in Go.

Goapnsfcmgcm
Uuniqush
1,6 k étoiles202

Send push notifications to your phone or desktop using PUT/POST

Gocurlnotificationsntfy
Bbinwiederhier
34,5 k étoiles1,6 k