Skip to main content

Firebase Cloud Messaging (FCM) Notification Sender

fcm-icon

A Python CLI tool for sending Firebase Cloud Messaging (FCM) notifications using the Firebase Admin SDK.

PyPI version Python 3.9+ License: MIT

Installation

This project uses Poetry for dependency management and packaging.

Add to your project:

poetry add fcm-send

Install globally:

pipx install fcm-send

Using pip

pip install fcm-send

Using pipx (Isolated CLI)

pipx installs the CLI in an isolated environment, avoiding dependency conflicts:

# Install pipx if you don't have it
pip install pipx
# macOS: Using brew
brew install pipx
pipx ensurepath

# Install fcm-send
pipx install fcm-send

From Source (Development)

git clone https://github.com/mfdeveloper/firebase_cloud_messaging_cli.git
cd firebase_cloud_messaging_cli

# Install with Poetry (recommended)
poetry install

# Run the CLI
poetry run fcm-send --version

Features

  • Send push notifications with title, body, and optional custom data
  • Send data-only messages (silent notifications for background processing)
  • Display service account info and retrieve the access token
  • Dry-run mode to validate messages without sending
  • Image support for rich notifications

Prerequisites

1. Install Poetry

If you don't have Poetry installed:

# macOS / Linux
curl -sSL https://install.python-poetry.org | python3 -

# Alternatively, create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate

# And using pipx
pipx install poetry

Note: After installation, ensure Poetry is in your PATH. See Poetry Installation.

2. Set Up the Project

cd <project-root>

# Install dependencies (creates .venv automatically)
poetry install

# Activate the virtual environment
poetry shell

Note: Poetry automatically creates a .venv folder in your project (configured in poetry.toml).

3. Firebase Service Account

To authenticate a service account and authorize it to access Firebase services, you must generate a service-account.json private key file in JSON format.

To generate and obtain one: a private key file for your service account:

  1. Go to Firebase Console
  2. Select your project
  3. Open Project Settings > Service Accounts.
  4. Click Generate New Private Key, then confirm by clicking Generate Key.
  5. Securely store the .json file containing the key.

For more details, see the Firebase documentation page: Initialize the SDK in non-Google environments

Environment Setup

Set the credentials environment variable pointing to your service account JSON file:

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your-service-account.json

Tip: Add this to your ~/.zshrc or ~/.bashrc for persistence.

External Variables Reference

Variable Description
GOOGLE_APPLICATION_CREDENTIALS Environment variable pointing to the service account .json file
ACCESS_TOKEN Temporary Firebase access token (retrieved automatically by the SDK)
FCM_TOKEN Device FCM registration token (obtained from mobile app)
SERVICE_ACCOUNT_EMAIL The client_email field from the credentials JSON
PROJECT_ID The project_id field from the credentials JSON

Usage

Running with Poetry

Before running commands, ensure you're in the Poetry environment:

cd <project-root>

# Option 1: Activate shell
poetry shell
fcm-send --info

# Option 2: Run directly with "poetry run"
poetry run fcm-send --info

Note: Using a code editor (such as VSCode or Cursor) a Python Virtual Environment can be loaded automatically using .vscode/settings.json configurations. Open this folder as ${workspaceFolder} and settings.json file mentioned above would be loaded!


Pass Google Credentials .json file

# Using CLI argument (takes precedence)
fcm-send --credentials-key-file ~/my-service-account.json --info

# Using environment variable
export GOOGLE_APPLICATION_CREDENTIALS=~/my-service-account.json
fcm-send --info

# Combining with other commands
fcm-send --credentials-key-file ~/sa.json --token FCM_TOKEN --title "Hi" --body "Test"

Show Service Account Info & Access Token

Display project details and retrieve the current Firebase Admin SDK access token:

fcm-send --info
# Or use --access-token alias
fcm-send --access-token

Output:

============================================================
Firebase Service Account Information
============================================================
  PROJECT_ID:            your-project-id
  SERVICE_ACCOUNT_EMAIL: firebase-adminsdk@your-project.iam.gserviceaccount.com
  CREDENTIALS_FILE:      /path/to/service-account.json
============================================================

  ACCESS_TOKEN (first 50 chars): ya29.c.c0ASRK0GYQ...
  ACCESS_TOKEN (full):
  ya29.c.c0ASRK0GYQ...
============================================================

Alternatively, display project details with current Google OAuth2 access token for using with FCM HTTP API access token. You can use it to perform a curl or any other way for HTTP requests on FCM API:

fcm-send --info-http
# Or use --access-token-http alias
fcm-send --access-token-http

Send a Simple Notification

fcm-send --token YOUR_FCM_TOKEN --title "Hello" --body "World"

Send Notification with Custom Data Payload

fcm-send --token YOUR_FCM_TOKEN \
  --title "Order Update" \
  --body "Your order has been shipped!" \
  --data '{"order_id": "12345", "action": "open_order"}'

Send Notification with Image

fcm-send --token YOUR_FCM_TOKEN \
  --title "Check this out" \
  --body "New feature available!" \
  --image "https://example.com/image.png"

Send Data-Only Message (Silent/Background)

Data-only messages don't show a visible notification but are delivered to your app for background processing:

fcm-send --token YOUR_FCM_TOKEN \
  --data-only '{"action": "sync", "resource_id": "123"}'

Validate Message Without Sending (Dry Run)

Test your message configuration without actually sending it:

fcm-send --token YOUR_FCM_TOKEN \
  --title "Test" \
  --body "This is a test" \
  --dry-run

Using as a Python Library

You can also use fcm-send as a library in your Python code:

from fcm_send import FCMClient

# Initialize client with credentials file
client = FCMClient("/path/to/service-account.json")

# Send a notification
response = client.send_notification(
    fcm_token="device_fcm_token",
    title="Hello",
    body="World",
    data={"key": "value"}  # optional
)
print(f"Message ID: {response}")

# Send a data-only message
response = client.send_data_message(
    fcm_token="device_fcm_token",
    data={"action": "sync", "id": "123"}
)

CLI Options Reference

Option Description
--credentials-key-file Path to Firebase service account .json file (CLI argument, takes over $GOOGLE_APPLICATION_CREDENTIALS)
--info Display service account info and access token
--info-http Display service account info and Google OAuth2 access token for FCM HTTP API
--access-token Alias for --info
--access-token-http Alias for --info-http
--token <FCM_TOKEN> FCM registration token of the target device
--title TEXT Notification title
--body TEXT Notification body
--data <JSON> Custom data payload as JSON string (e.g --data '{"action": "sync" }')
--data-only <JSON> Send data-only message (no visible notification)
--image URL Image URL for rich notifications
--dry-run Validate message without sending

Getting the FCM Token from Your App

To send notifications, you need the FCM registration token from your mobile app. In Android:

FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
    if (task.isSuccessful) {
        val token = task.result
        Log.d("FCM", "Token: $token")
    }
}

Reference: FCM: Retrieve the current registration token

Troubleshooting

"GOOGLE_APPLICATION_CREDENTIALS environment variable not set"

Make sure you've exported the environment variable:

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

Reference: Firebase Admin SDK: Initialize in non-Google environments

"The FCM token is not registered"

This error occurs when:

  • The app has been uninstalled from the device
  • The token has expired or been rotated
  • The token was generated for a different Firebase project

"The FCM token does not match the sender ID"

The FCM token was generated for a different Firebase project. Make sure you're using:

  • The correct service account JSON for your project
  • An FCM token generated by an app configured with the same Firebase project

Class Structure

The script is organized into two main classes:

FCMClient

Handles all Firebase operations:

  • credentials_path - Get credentials file path from environment
  • credentials_info - Load/cache credentials JSON
  • project_id - Get project ID from credentials
  • service_account_email - Get service account email
  • initialize() - Initialize Firebase Admin SDK
  • get_access_token() - Retrieve current access token
  • show_info() - Display service account information
  • send_notification() - Send FCM notification
  • send_data_message() - Send data-only message

CLIHandler

Handles command-line interface:

  • create_parser() - Configure argument parser
  • handle_info() - Process --info command
  • handle_data_only() - Process --data-only command
  • handle_notification() - Process notification command
  • run() - Main CLI execution logic

Development

Package Structure

firebase-cloud-messaging/
├── src/
│   └── fcm_send/
│       ├── __init__.py      # Package exports (FCMClient, main, etc.)
│       ├── __main__.py      # Enables `python -m fcm_send`
│       ├── __version__.py   # Version from pyproject.toml ✨
│       ├── client.py        # FCMClient class
│       ├── cli.py           # CLIHandler and main() entry point
│       └── py.typed         # PEP 561 marker for type checking
├── tests/                   # Test suite (58 tests)
├── pyproject.toml           # Poetry configuration
├── poetry.toml              # Poetry settings (in-project venv)
├── poetry.lock              # Locked dependencies
├── LICENSE                  # MIT License
└── README.md                # This file

Installation for Development

git clone https://github.com/mfdeveloper/firebase_cloud_messaging_cli.git
cd firebase_cloud_messaging_cli

# Install all dependencies (creates .venv automatically)
poetry install

# Activate the virtual environment
poetry shell

# Or run commands directly
poetry run fcm-send --version
poetry run pytest

Using pip (Alternative)

git clone https://github.com/mfdeveloper/firebase_cloud_messaging_cli.git
cd firebase_cloud_messaging_cli

# Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install from requirements.txt
pip install -r requirements.txt

# Or install the package in editable mode
pip install -e .

Unit Testing

The project includes a comprehensive test suite with 58 unit tests achieving 97% code coverage.

# Run all tests
poetry run pytest

# Run with coverage terminal report
poetry run pytest --cov=src/fcm_send --cov-report=term-missing

# Run tests with coverage (HTML report)
poetry run pytest --cov=src/fcm_send --cov-report=html

For detailed testing documentation, including test breakdown, fixtures, and mocking strategy, see:

📄 tests/README.md

Code Linting

This project uses Pylint for static code analysis. The configuration is defined in .pylintrc.

Running Pylint

# Lint source package only
poetry run pylint src/fcm_send/

# Lint source package and tests
poetry run pylint src/fcm_send/ tests/

# Lint tests only
poetry run pylint tests/

Configuration

The .pylintrc file includes project-specific settings:

  • Max line length: 120 characters
  • Extended test names: Supports long descriptive test method names
  • Disabled rules: Common pytest patterns (unused fixtures, redefined outer names) and CLI patterns (broad exception catching)

To check your current score:

# Output: Your code has been rated at X.XX/10
poetry run pylint src/fcm_send/ tests/

Publishing to PyPI

This project uses Poetry for building and publishing packages.

1. Update Version

Update the version in pyproject.toml:

[tool.poetry]
version = "0.2.0"

Or use Poetry's version command:

# Bump patch version (0.1.0 → 0.1.1)
poetry version patch

# Bump minor version (0.1.0 → 0.2.0)
poetry version minor

# Bump major version (0.1.0 → 1.0.0)
poetry version major

2. Build the Package

# Build both sdist and wheel
poetry build

This creates distribution files in the dist/ directory:

  • dist/fcm_send-0.1.0.tar.gz (source distribution)
  • dist/fcm_send-0.1.0-py3-none-any.whl (wheel)

Test your package on Test PyPI before publishing to the real PyPI.

This project includes a GitHub Actions workflow for automated publishing to TestPyPI.

Triggers:

  • Manual: Go to Actions → "Publish to TestPyPI" → "Run workflow"
  • Automatic: Push a version tag (e.g., git tag v0.1.0 && git push --tags)

One-time Setup:

  1. Create a TestPyPI account
  2. Generate an API token at TestPyPI API Tokens
  3. Add the token as a GitHub secret:
    • Go to your repository Settings → Secrets and variables → Actions
    • Create a new secret named TEST_PYPI_API_TOKEN with your token

Option B: Manual Upload with Poetry

# Configure TestPyPI repository
poetry config repositories.testpypi https://test.pypi.org/legacy/

# Set your TestPyPI token
poetry config pypi-token.testpypi pypi-XXXXXXXXXXXX

# Publish to TestPyPI
poetry publish --repository testpypi

# Alternatively, you can use regular "Twine" for publishing
twine upload --repository testpypi dist/*

# Test installation from TestPyPI
pip install --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  fcm-send

Note: The --extra-index-url is needed because TestPyPI doesn't have all dependencies (like firebase-admin), so it falls back to the real PyPI for those.

4. Upload to PyPI

Once verified on TestPyPI, publish to the official PyPI.

This project includes a GitHub Actions workflow for automated publishing to PyPI.

Trigger:

  • Push a pypi-* tag:
    git tag -a pypi-0.1.0 -m "Release version 0.1.0"
    git push origin pypi-0.1.0
    

One-time Setup:

  1. Create a PyPI account
  2. Generate an API token at PyPI API Tokens
  3. Add the token as a GitHub secret:
    • Go to your repository Settings → Secrets and variables → Actions
    • Create a new secret named PYPI_API_TOKEN with your token

Option B: Manual Upload with Poetry

# Set your PyPI token
poetry config pypi-token.pypi pypi-XXXXXXXXXXXX

# Publish to PyPI
poetry publish

# Alternatively, you can use regular "Twine" for publishing
twine upload dist/*

5. After Publishing

Users can install your package with:

poetry add fcm-send
# or
pip install fcm-send

Poetry Commands Reference

Command Description
poetry install Install all dependencies
poetry install --extras dev Install with dev dependencies
poetry shell Activate the virtual environment
poetry run <cmd> Run a command in the virtual environment
poetry add <pkg> Add a dependency
poetry add --group dev <pkg> Add a dev dependency
poetry remove <pkg> Remove a dependency
poetry update Update dependencies to latest versions
poetry lock Update poetry.lock without installing
poetry build Build sdist and wheel
poetry publish Publish to PyPI
poetry version <rule> Bump version (patch, minor, major)
poetry show Show installed packages
poetry env info Show virtualenv info

References

Release files for fcm-send 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fcm-send 0.1.1
File Size Uploaded
fcm_send-0.1.1.tar.gz 16.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fcm-send 0.1.1
File Interpreter ABI Platform
fcm_send-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 31.0 kB

Release files / fcm_send-0.1.1.tar.gz

Download URL fcm_send-0.1.1.tar.gz
Size 16.7 kB
Tags Source
SHA-256 checksum
How to use checksums
cee605f26c7a54fffa38030aa089cd11a31b81479c39040118431827b260a55d
BLAKE2b-256 checksum
How to use checksums
60be42b966b6e2c3860c308073f9ffe3016dadf8c713abe027dd96396b4dd56e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Dec 17, 2025.

Transparency log

Release files / fcm_send-0.1.1-py3-none-any.whl

Download URL fcm_send-0.1.1-py3-none-any.whl
Size 14.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
78f39310dbb780e3bce3d7b7c11ecd067e836013e3c4b2237feeb73402291e9e
BLAKE2b-256 checksum
How to use checksums
6c15a226cb311e694f4915ac4030a119ff0049c58ef2e5e08b94c0b9fff34807
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Dec 17, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page