go-notificationgo-notification
Channels

Push (Firebase FCM)

Mobile push notifications via Firebase Cloud Messaging. iOS, Android, and Web.

Mobile push is handled by Firebase Cloud Messaging (FCM). One driver, all three platforms (iOS via APNs under the hood, Android directly, and Web via browser push).

Pricing

Free. FCM has no per-message cost for any volume. You pay for whatever storage/analytics features you enable on Firebase, but message delivery itself is unlimited and free.

Setup

  1. Create a Firebase project at https://console.firebase.google.com.
  2. Add your apps (iOS, Android, Web).
  3. For iOS, upload your APNs authentication key (P8) in Project Settings → Cloud Messaging.
  4. For Android, FCM works out of the box once google-services.json is in the app.
  5. Generate a service account JSON (Project Settings → Service accounts → Generate new private key).
main.go
import (
    "github.com/gopackx/go-notification/channel/push"
    "github.com/gopackx/go-notification/channel/push/fcm"
)

driver, err := fcm.New(fcm.Config{
    ProjectID:          "my-firebase-project",
    ServiceAccountJSON: []byte(os.Getenv("FCM_SERVICE_ACCOUNT_JSON")),
})
if err != nil {
    panic(err)
}
notifier.RegisterChannel(driver)

FCM is the only driver whose New returns (*Driver, error) — it parses the service-account key up front. Capture and check the error; you can't inline it into RegisterChannel.

Storing the full JSON in an env var is fine for smaller setups; for anything serious, fetch it from a secrets manager at startup.

Sending

main.go
func (n ChatMessage) Via(u notification.Notifiable) []string {
    return []string{"push"}
}

func (n ChatMessage) ToPush(u notification.Notifiable) *push.Message {
    return push.NewMessage().
        SetTitle(n.From).
        SetBody(n.Preview).
        SetData("conversation_id", n.ConversationID). // string values only
        SetBadge(1).
        SetSound("default")
}

func (u User) RouteNotificationFor(channel string) string {
    if channel == "push" {
        return u.DeviceToken // the user's current FCM token
    }
    return ""
}

RouteNotificationFor returns a single token. To target several devices in one call, set them in ToPush with SetTokens(...) (FCM multicast); use SetTopic(...) for topic delivery.

main.go
return push.NewMessage().SetTokens(t1, t2, t3).SetTitle("Shipped")

Configuration reference

FieldTypeRequiredDescription
ProjectIDstringyesGCP project ID that owns the FCM app.
ServiceAccountJSON[]byteyesRaw bytes of the GCP service-account key file.
Timeouttime.DurationnoHTTP timeout per send. Default: 30s.
NamestringnoOverride the channel name. Default: "push".

Message fields

The push.Message builder is platform-agnostic — FCM maps these to each platform:

main.go
push.NewMessage().
    SetTitle("Alert").
    SetBody("Your order shipped").
    SetImage("https://example.com/hero.png").
    SetSound("default").
    SetBadge(1).
    SetClickAction("OPEN_ORDERS").
    SetData("order_id", "A-1024") // string→string only

Targeting: SetToken(one), SetTokens(many...) (multicast), or SetTopic(name) — mutually exclusive.

Token lifecycle

  • Register tokens at app startup / after login and store them per user.
  • Remove a token when FCM returns UNREGISTERED or INVALID_ARGUMENT on send — the device uninstalled the app or the token rotated. The driver surfaces these in OnError.
  • Refresh — FCM may rotate tokens; the client SDK emits a onTokenRefresh event. Send the new token to your backend and replace the old one.

Troubleshooting

  • UNREGISTERED — user uninstalled the app or logged out. Remove the token from your database.
  • QUOTA_EXCEEDED — rare; usually from sending to too many tokens in one multicast. Split into smaller batches.
  • Silent pushes not delivered on iOS — Apple heavily throttles silent/content-available pushes. Don't rely on them for anything critical.
  • Notifications work in dev, fail in TestFlight/prod — wrong APNs environment (sandbox vs production) in the service account config, or APNs P8 key uploaded to wrong project.