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
- Self-hosted (Docker Compose), Postgres backend, FCM configured (
FLUXER_PUSH_FCM_*). - Register a native Android FCM device; send it any message.
- 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
Comment by @Coding-Kiwi
Comment by Hampus