Self-hosted native FCM (Android) push silently dropped - invalid `group` field in android.notification -> FCM HTTP 400

(#745) Bug Fixed mobile notifications self-hosting

Summary

On a self-hosted instance, native-app push over FCM is silently dropped for every message. Devices registered with platform android_fcm never receive DM or channel notifications, even though the push subscription is valid and registered. Root cause: the gateway builds an FCM HTTP v1 message containing a field (group) that does not exist in FCM's AndroidNotification schema, so the FCM REST API rejects the entire request with HTTP 400. push_fcm swallows non-2xx responses with no log, so there is zero visibility - the gateway logs a complete, healthy-looking send path (evaluating eligibility -> fetching subscriptions -> sending to subscription -> worker completed) while every single message is 400'd.

Root cause

fluxer_gateway/src/push/push_fcm_payload.erl, build_android_notification/3:
build_android_notification(Tag, ImageUrl, Group) ->
    maybe_put(<<"image">>, ImageUrl, #{
        <<"channel_id">> => <<"fluxer_default_push">>,
        <<"tag">> => Tag,
        <<"group">> => Group,          %% <-- not a valid FCM v1 AndroidNotification field
        <<"click_action">> => <<"FLUXER_MESSAGE">>
    }).
FCM v1's AndroidNotification has no group field (Android notification grouping is a client-side concern via NotificationCompat.setGroup). FCM responds:
{ "error": { "code": 400, "status": "INVALID_ARGUMENT",
  "message": "Invalid JSON payload received. Unknown name \"group\" at 'message.android.notification': Cannot find field." } }

Why it's invisible

  • push_fcm_payload:handle_response/3 returns false silently on any non-2xx (no log line).
  • The self-hosted gateway image emits no logs at all: releases/*/sys.config sets the default logger handler to undefined, and docker_entrypoint.sh runs exec … foreground >/dev/null 2>&1 (see #665).
So the failure is completely silent end-to-end.

Reproduction

  1. Self-hosted (Docker Compose), Postgres backend, FCM configured (FLUXER_PUSH_FCM_*).
  2. Register a native Android FCM device; send it any message.
  3. Nothing arrives. A raw FCM v1 send to the same device token with the same payload shape reproduces it deterministically: with android.notification.group -> HTTP 400; remove it -> HTTP 200 and the notification is delivered.

Fix

Drop the group field. collapse_key (set in wrap_notification_message, a valid AndroidConfig field) still handles collapsing:
build_android_notification(Tag, ImageUrl) ->
    maybe_put(<<"image">>, ImageUrl, #{
        <<"channel_id">> => <<"fluxer_default_push">>,
        <<"tag">> => Tag,
        <<"click_action">> => <<"FLUXER_MESSAGE">>
    }).
Verified on a live self-hosted instance: native Android DM/channel push now delivers, tap-through works. iOS/APNs (ApnsPushService) and web-push/VAPID use different payload builders and are unaffected by this field.

Bonus - getting gateway logs (re #665)

Because the image discards all logs, you can attach a file logger to the running node at runtime via the release's RPC (no restart, fully reversible):
# with FLUXER_ERLANG_NODE_NAME / FLUXER_ERLANG_COOKIE / FLUXER_ERLANG_DIST_PORT
# set to the running node's values (entrypoint defaults: fluxer_gateway@127.0.0.1 /
# fluxer_gateway_dev_cookie / 8081):
bin/fluxer_gateway.real rpc logger add_handler \
  '[gwlog, logger_std_h, #{level => debug, config => #{file => "/tmp/gw.log"}}]'
bin/fluxer_gateway.real rpc logger set_primary_config '[level, debug]'
# ... read /tmp/gw.log ...
bin/fluxer_gateway.real rpc logger remove_handler '[gwlog]'
(Note: erl_call, the transport behind rpc, cannot parse binary literals - pass only integers/atoms/strings/simple maps.)

Environment

  • Self-hosted, Docker Compose, Postgres, ghcr.io/fluxerapp/fluxer-gateway.
  • Client: native flutter_client Android app (FCM). group present in upstream/main since the open-source re-architecture, so this affects all self-hosted FCM deployments.
(Diagnosis performed with Claude Code.)

2 comments

Sign in with Fluxer to comment and vote.
Comment by @Coding-Kiwi
RexSystem 1 vote edited originally by @Coding-Kiwi on GitHub
Hi, I am facing a similar issue but I am using the unified push variant. Thanks to your "bonus" I was able to get some logs
fluxer@e401e57a394d:~$ export FLUXER_ERLANG_NODE_NAME=fluxer_gateway@127.0.0.1
fluxer@e401e57a394d:~$ export FLUXER_ERLANG_COOKIE=fluxer_gateway_dev_cookie
fluxer@e401e57a394d:~$ export FLUXER_ERLANG_DIST_PORT=8081
fluxer@e401e57a394d:~$ bin/fluxer_gateway.real rpc logger add_handler  '[gwlog, logger_std_h, #{level => debug, config => #{file => "/tmp/gw.log"}}]'
ok
fluxer@e401e57a394d:~$ bin/fluxer_gateway.real rpc logger set_primary_config '[level, debug]'
ok
fluxer@e401e57a394d:~$ tail -f /tmp/gw.log 
but when sending a system DM this is the only output I get
2026-07-25T10:15:19.247191+00:00 debug: got data: <<"MSG rpc.gateway.presence.dispatch 1 _INBOX.redacted 534\r\n{\"user_id\":\"redacted\",\"event\":\"MESSAGE_CREATE\",\"data\":{\"id\":\"redacted\",\"channel_id\":\"redacted\",\"author\":{\"id\":\"0\",\"username\":\"Fluxer\",\"discriminator\":\"0000\",\"global_name\":null,\"avatar\":null,\"avatar_color\":null,\"bot\":true,\"system\":true,\"flags\":1},\"type\":0,\"flags\":0,\"content\":\"this is a test\",\"timestamp\":\"2026-07-25T10:15:19.196Z\",\"edited_timestamp\":null,\"pinned\":false,\"mention_everyone\":false,\"tts\":false,\"mentions\":[],\"mention_roles\":[],\"embeds\":[],\"attachments\":[],\"stickers\":[],\"channel_type\":1}}\r\n">>

2026-07-25T10:15:19.247418+00:00 debug: got msg: {msg,{<<"rpc.gateway.presence.dispatch">>,<<"1">>,<<"_INBOX.redacted">>,<<"{\"user_id\":\"redacted\",\"event\":\"MESSAGE_CREATE\",\"data\":{\"id\":\"redacted\",\"channel_id\":\"redacted\",\"author\":{\"id\":\"0\",\"username\":\"Fluxer\",\"discriminator\":\"0000\",\"global_name\":null,\"avatar\":null,\"avatar_color\":null,\"bot\":true,\"system\":true,\"flags\":1},\"type\":0,\"flags\":0,\"content\":\"this is a test\",\"timestamp\":\"2026-07-25T10:15:19.196Z\",\"edited_timestamp\":null,\"pinned\":false,\"mention_everyone\":false,\"tts\":false,\"mentions\":[],\"mention_roles\":[],\"embeds\":[],\"attachments\":[],\"stickers\":[],\"channel_type\":1}}">>}}
There is no "Push: sending to user subscriptions" message, eventhough the user has a valid subscription (I checked in the fluxer_kv postgres database) So it seems the server doesn't even try? Is this the problem mentioned in #723 ?