GoFCM is a production-ready, opinionated wrapper for the official Firebase Admin SDK (v4).
It is designed specifically for Cross-Platform Mobile Apps (Flutter, React Native) to solve the common "Double Notification" issue on Android while guaranteeing delivery on iOS.
If you send a standard Notification payload to a Flutter app, Android devices often display the notification twice: once from the System Tray and once from the App's local notification plugin.
GoFCM automatically handles this complexity:
| Feature | Standard Firebase SDK | GoFCM (This Library) |
|---|---|---|
| Android Strategy | Sends Notification (Causes Duplicates) |
Sends Data-Only (High Priority) |
| iOS Strategy | Sends Notification |
Sends APNS Alert (Guarantees Delivery) |
| Data Mirroring | Manual | Automatic (Title/Body copied to Data) |
| Batch Sending | Manual implementation | Automatic Batching (500 tokens/req) |
| Bad Token Handling | Manual cleanup | Built-in Callback Handler |
go get github.com/Dostonlv/go-fcmpackage main
import (
"context"
"log"
"[github.com/your-username/gofcm](https://github.com/your-username/gofcm)"
)
func main() {
ctx := context.Background()
// 1. Initialize Client
client, err := gofcm.New(ctx,
gofcm.WithCredentialsFile("./serviceAccountKey.json"),
)
if err != nil {
log.Fatal(err)
}
// 2. Create Message
// You don't need to worry about platform specifics; the library handles it.
msg := gofcm.Message{
Title: "Order Updated 📦",
Body: "Your order #1234 is now shipping!",
Data: map[string]string{
"order_id": "1234",
"type": "status_update",
},
Android: &gofcm.AndroidConfig{
ChannelID: "orders_channel", // Required for Android 8+
},
}
// 3. Send to Tokens
tokens := []string{"fcm_token_1", "fcm_token_2"}
err = client.Send(ctx, tokens, msg, func(badToken string) {
// Callback for cleaning up invalid tokens
log.Printf("Removing invalid token from DB: %s", badToken)
})
if err != nil {
log.Printf("Error sending batch: %v", err)
}
}GoFCM uses the Functional Options Pattern for flexible configuration.
You can load credentials from a file path or a raw JSON byte slice (useful for environment variables in Docker/Kubernetes).
// Option A: From File (Standard)
gofcm.New(ctx, gofcm.WithCredentialsFile("path/to/key.json"))
// Option B: From JSON Bytes (Secure/Env Vars)
jsonKey := []byte(os.Getenv("FIREBASE_CREDENTIALS"))
gofcm.New(ctx, gofcm.WithCredentialsJSON(jsonKey))By default, GoFCM uses the standard log package. In production, you likely use a structured logger. You can inject any logger that satisfies the simple Logger interface.
Interface Definition:
type Logger interface {
Printf(format string, v ...interface{})
}// 1. Define an adapter
type ZapAdapter struct {
Sugar *zap.SugaredLogger
}
func (z *ZapAdapter) Printf(format string, v ...interface{}) {
z.Sugar.Infof(format, v...) // Redirect internal logs to Zap
}
// 2. Pass it to the client
func main() {
logger, _ := zap.NewProduction()
sugar := logger.Sugar()
client, _ := gofcm.New(ctx,
gofcm.WithCredentialsFile("key.json"),
gofcm.WithLogger(&ZapAdapter{Sugar: sugar}),
)
}If your server is located in Russia (Timeweb, Reg.ru, etc.), Google APIs are typically blocked. You must use a proxy from another region (e.g., Germany, Netherlands, or USA).
Why WithHTTP2Proxy?
- Multiplexing: Sends multiple notifications over a single connection even through the proxy.
- Smart Fallback: Automatically switches to HTTP/1.1 if the proxy doesn't support HTTP/2.
client, err := gofcm.New(ctx,
gofcm.WithCredentialsFile("key.json"),
// Format: http://user:password@ip:port
gofcm.WithHTTP2Proxy("http://myuser:mypass@1.2.3.4:8080"),
)
# Example: load proxy from .env
proxy := os.Getenv("FCM_PROXY_URL")
client, err := gofcm.New(ctx,
gofcm.WithCredentialsFile("key.json"),
gofcm.WithHTTP2Proxy(proxy),
)Topic messaging allows you to broadcast a single message to a large number of devices that have opted into a specific interest group without needing to manage a list of individual tokens on your server.
// 1. Subscribe a list of tokens to a topic
// Note: Firebase supports up to 1000 tokens per single subscription call.
tokens := []string{"token_123", "token_456"}
err := client.SubscribeToTopic(ctx, tokens, "news_updates")
// 2. Broadcast a message to the entire topic
// The Hybrid Strategy applies here as well (Data-only for Android, Alert for iOS).
msg := gofcm.Message{
Title: "Breaking News 🚨",
Body: "Something important happened!",
Android: &gofcm.AndroidConfig{
Priority: gofcm.DeliveryHigh,
},
}
err = client.SendToTopic(ctx, "news_updates", msg)
// 3. Unsubscribe tokens from a topic
client.UnsubscribeFromTopic(ctx, []string{"token_123"}, "news_updates")