Categorygithub.com/appleboy/go-fcm
repositorypackage
1.2.6
Repository: https://github.com/appleboy/go-fcm.git
Documentation: pkg.go.dev

# README

go-fcm

English | 繁體中文 | 简体中文

GoDoc Lint and Testing Go Report Card

Forked from github.com/edganiukov/fcm
Firebase Cloud Messaging Official Documentation


Table of Contents


Features

FeatureSupportedDescription
Single Device MessagingSend messages to a single device
Multiple Device MessagingSend messages to multiple devices
Topic MessagingSend messages to a specific topic
Condition MessagingSupport for FCM condition syntax
Custom HTTP ClientCustom timeout, proxy, and transport config
Multiple Message FormatsData, Notification, Multicast
Unit Test & Mock SupportEasy unit testing with mock client

Supported Message Types

TypeDescription
DataCustom data messages, handled by the app
NotificationSystem notification messages, shown in notification bar
MulticastSend to up to 500 device tokens at once
TopicSend to all devices subscribed to a topic
ConditionSend to devices matching a logical condition

Quick Start

Install go-fcm:

go get github.com/appleboy/go-fcm

Authentication and Credentials

It is recommended to use Google Application Default Credentials (ADC) for authentication.
Download the JSON key from Firebase Console > Settings > Service Accounts and set the environment variable:

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/serviceAccountKey.json"

Alternatively, specify the key path directly in your code.


Usage Example

package main

import (
  "context"
  "fmt"
  "log"

  "firebase.google.com/go/v4/messaging"
  fcm "github.com/appleboy/go-fcm"
)

func main() {
  ctx := context.Background()
  client, err := fcm.NewClient(
    ctx,
    fcm.WithCredentialsFile("path/to/serviceAccountKey.json"),
    // fcm.WithServiceAccount("[email protected]"),
  )
  if err != nil {
    log.Fatal(err)
  }

  // Send to a single device
  token := "YOUR_DEVICE_TOKEN"
  resp, err := client.Send(
    ctx,
    &messaging.Message{
      Token: token,
      Data: map[string]string{
        "foo": "bar",
      },
    },
  )
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println("Success:", resp.SuccessCount, "Failure:", resp.FailureCount)

  // Send to a topic
  resp, err = client.Send(
    ctx,
    &messaging.Message{
      Data: map[string]string{
        "foo": "bar",
      },
      Topic: "highScores",
    },
  )
  if err != nil {
    log.Fatal(err)
  }

  // Send with condition
  condition := "'stock-GOOG' in topics || 'industry-tech' in topics"
  message := &messaging.Message{
    Data: map[string]string{
      "score": "850",
      "time":  "2:45",
    },
    Condition: condition,
  }
  resp, err = client.Send(ctx, message)
  if err != nil {
    log.Fatal(err)
  }

  // Send to multiple devices
  registrationToken := "YOUR_REGISTRATION_TOKEN"
  messages := []*messaging.Message{
    {
      Notification: &messaging.Notification{
        Title: "Price drop",
        Body:  "5% off all electronics",
      },
      Token: registrationToken,
    },
    {
      Notification: &messaging.Notification{
        Title: "Price drop",
        Body:  "2% off all books",
      },
      Topic: "readers-club",
    },
  }
  resp, err = client.Send(ctx, messages...)
  if err != nil {
    log.Fatal(err)
  }

  // Multicast messaging
  registrationTokens := []string{
    "YOUR_REGISTRATION_TOKEN_1",
    "YOUR_REGISTRATION_TOKEN_2",
    // ...
  }
  msg := &messaging.MulticastMessage{
    Data: map[string]string{
      "score": "850",
      "time":  "2:45",
    },
    Tokens: registrationTokens,
  }
  resp, err = client.SendMulticast(ctx, msg)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Printf("%d messages were sent successfully\n", resp.SuccessCount)
  if resp.FailureCount > 0 {
    var failedTokens []string
    for idx, resp := range resp.Responses {
      if !resp.Success {
        failedTokens = append(failedTokens, registrationTokens[idx])
      }
    }
    fmt.Printf("List of tokens that caused failures: %v\n", failedTokens)
  }
}

Advanced Configuration

Custom HTTP Client

import (
  "crypto/tls"
  "net"
  "net/http"
  "time"
  "golang.org/x/net/http2"
)

func main() {
  httpTimeout := 5 * time.Second
  tlsTimeout := 5 * time.Second

  transport := &http2.Transport{
    DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
      return tls.DialWithDialer(&net.Dialer{Timeout: tlsTimeout}, network, addr, cfg)
    },
  }

  httpClient := &http.Client{
    Transport: transport,
    Timeout:   httpTimeout,
  }

  ctx := context.Background()
  client, err := fcm.NewClient(
    ctx,
    fcm.WithCredentialsFile("path/to/serviceAccountKey.json"),
    fcm.WithHTTPClient(httpClient),
  )
}

Proxy Support

func main() {
  ctx := context.Background()
  client, err := fcm.NewClient(
    ctx,
    fcm.WithCredentialsFile("path/to/serviceAccountKey.json"),
    fcm.WithHTTPProxy("http://localhost:8088"),
  )
}

Unit Testing and Mock

import (
  "context"
  "net/http"
  "net/http/httptest"
  "testing"

  "firebase.google.com/go/v4/messaging"
  fcm "github.com/appleboy/go-fcm"
  "google.golang.org/api/option"
)

func TestMockClient(t *testing.T) {
  server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Header().Set("Content-Type", "application/json")
    _, _ = w.Write([]byte(`{"name": "q1w2e3r4"}`))
  }))
  defer server.Close()

  client, err := fcm.NewClient(
    context.Background(),
    fcm.WithEndpoint(server.URL),
    fcm.WithProjectID("test"),
    fcm.WithCustomClientOption(option.WithoutAuthentication()),
  )
  if err != nil {
    t.Fatalf("unexpected error: %v", err)
  }
  resp, err := client.Send(
    context.Background(),
    &messaging.Message{
      Token: "test",
      Data: map[string]string{
        "foo": "bar",
      },
    })
  if err != nil {
    t.Fatalf("unexpected error: %v", err)
  }
  // Check response
  if resp.SuccessCount != 1 {
    t.Fatalf("expected 1 successes, got: %d", resp.SuccessCount)
  }
  if resp.FailureCount != 0 {
    t.Fatalf("expected 0 failures, got: %d", resp.FailureCount)
  }
}

Best Practices

[!TIP]

  • For batch messaging, limit each batch to no more than 500 tokens.
  • Prefer using Topics to manage device groups instead of direct token management.
  • Set your credentials file as read-only and store it securely.

Troubleshooting

Error CodePossible Cause & Solution
UNREGISTEREDToken is invalid or expired, remove from DB
INVALID_ARGUMENTInvalid message format, check your payload
QUOTA_EXCEEDEDFCM quota exceeded, try again later
UNAUTHORIZEDInvalid credentials, check your key and access
INTERNALFCM server error, retry the request

Architecture Diagram

flowchart TD
    A[Your Go Server] -->|go-fcm| B[FCM API]
    B --> C[Android/iOS/Web Device]
    B -.-> D[Topic/Condition Routing]

FAQ

  • Q: How do I obtain an FCM device token?
  • Q: How do I set up multi-language notifications?
  • Q: How do I track message delivery status?

For more, see the Firebase Official FAQ


License

This project is licensed under the MIT License.