Pushbrain is a multi-tenant push notification service. You bring your own Firebase project, drop a few lines into your app, and broadcast notifications from a dashboard or HTTP API. Encrypted credentials, your data, your FCM tenant — we just route the send.
See a real push notification arrive in this browser before connecting any Firebase project of your own. The token this creates is used once to send you one message, then discarded — nothing is stored.
For the full Firebase integration guide — SDK snippets, service-account setup, and campaign workflows — see Firebase integration →.
Project-level `build.gradle.kts`:
plugins {
id("com.google.gms.google-services") version "4.4.2" apply false
}plugins {
id("com.android.application")
id("kotlin-android")
id("com.google.gms.google-services")
}
dependencies {
implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
implementation("com.google.firebase:firebase-messaging-ktx")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
}<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Debug-only if your API_BASE is plain http:// on Android 9+. Prefer HTTPS in production. -->
<application android:usesCleartextTraffic="true" ... />Call this from your first Activity before testing pushes.
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
private fun requestNotificationPermission() {
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1001)
}
}Create this file anywhere in your `app/src/main/java/...` package.
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import com.google.firebase.messaging.FirebaseMessaging
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
object Pushbrain {
private const val SDK_KEY = "pb_YOUR_KEY_HERE"
private const val API_BASE = "/api"
private val http = OkHttpClient()
fun init(context: Context) {
createNotificationChannel(context)
val installId = getOrCreateInstallId(context)
FirebaseMessaging.getInstance().token
.addOnSuccessListener { token -> register(token, installId) }
.addOnFailureListener { e -> Log.w("Pushbrain", "getToken failed", e) }
}
/** Fires the open-rate beacon when a notification tap delivers trackUrl in intent extras. */
fun trackOpenFromIntent(intent: Intent?) {
if (intent == null) return
val trackUrl = intent.getStringExtra("trackUrl")
?: intent.getStringExtra("notifId")?.let { "$API_BASE/t/o?n=$it" }
if (trackUrl != null) fireOpenBeacon(trackUrl)
}
private fun fireOpenBeacon(trackUrl: String) {
http.newCall(Request.Builder().url(trackUrl).build()).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: java.io.IOException) {}
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) = response.close()
})
}
/** Stable per-install id, persisted locally. Lets Pushbrain recognize this
* device again after its FCM token rotates instead of double-counting it. */
private fun getOrCreateInstallId(context: Context): String {
val prefs = context.getSharedPreferences("pushbrain", Context.MODE_PRIVATE)
return prefs.getString("install_id", null) ?: java.util.UUID.randomUUID().toString().also {
prefs.edit().putString("install_id", it).apply()
}
}
private fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val channel = NotificationChannel(
"default",
"Default",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Pushbrain push notifications"
}
context.getSystemService(NotificationManager::class.java)
.createNotificationChannel(channel)
}
private fun register(fcmToken: String, installId: String) {
val body = JSONObject().apply {
put("sdkKey", SDK_KEY)
put("fcmToken", fcmToken)
put("platform", "android")
put("installId", installId)
put("appVersion", BuildConfig.VERSION_NAME)
put("environment", if (BuildConfig.DEBUG) "local" else "production")
put("deviceLabel", "${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}")
put("userEmail", currentUserEmailOrNull) // Optional: app user's email, if signed in
put("userName", currentUserNameOrNull) // Optional: app user's display name
// Optional location — enable Location collection in the dashboard first:
// put("city", currentUserCityOrNull)
// put("lat", currentUserLatOrNull)
// put("lng", currentUserLngOrNull)
put("permissionStatus", "granted")
put("timezone", java.util.TimeZone.getDefault().id)
put("sdkVersion", "android-snippet-1")
}.toString().toRequestBody("application/json".toMediaType())
http.newCall(
Request.Builder()
.url("$API_BASE/sdk/register-token")
.post(body)
.build()
).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: java.io.IOException) =
Log.w("Pushbrain", "register failed", e)
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
Log.d("Pushbrain", "registered: ${response.code}")
response.close()
}
})
}
}In your `Application` subclass (register it in `AndroidManifest.xml` as `android:name=".MyApp"`):
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Pushbrain.init(this)
}
}Open counts only increase when a user taps the notification. Forward launch intents from `MainActivity` so Android tray taps fire the open beacon. Opening the app icon manually does not count as a notification open.
import android.content.Intent
import android.os.Bundle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Pushbrain.trackOpenFromIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
Pushbrain.trackOpenFromIntent(intent)
}To power weather-gated Automations (Pro), enable Location collection in Integration and send city and/or lat/lng on registration. Preview weather for free with Integration → Test weather notification. Full guide: /docs/weather-notifications
Build and run on a phone, accept the notification permission prompt, and confirm the backend logs POST /sdk/register-token. Then refresh this dashboard — the device count ticks up within ~5 seconds. Background or lock the phone, send a notification, then tap the notification itself. Opening the app icon manually will not increase opens.
Already shipped to users? Drop Pushbrain into a normal release — no data migration, no parallel backend, no user disruption.
Try weather for free with Integration → Test weather notification(test devices only). Production weather-gated Automations that filter by each user's live weather require Pro.