PushMesh
Sign in Request access
Open section navigation

Index

Migrating from another provider

What changes when you switch push providers — what the API accepts as-is, what has to change in your app, and the steps nobody automates.

Migrating from another provider

Switching push providers usually means rewriting the entire integration layer. Not here: PushMesh’s /api/v1 speaks the classic push format the market already uses — the player model, with include_player_ids, contents, included_segments and friends.

In practice, the server side is repointing a base URL and swapping a key. The app side takes more work, and this page is honest about it: there are manual steps, and one stage that depends on your app update curve. Nobody migrates a device base with an API key.


The honest picture in three lines

LayerReal effort
Server (sending, polling, reporting)low — base URL + key
App (SDK)medium — swap the package and the init call
Device basea period, not a command — devices register as the app updates

Part 1 — The server

What is already in the format you know

Sending, POST /api/v1/notifications, with the classic names:

GroupAccepted fields
Target (exactly one)include_player_ids, include_external_user_ids, included_segments
Contentcontents, headings, subtitle, data
Tap destinationurl, app_url, web_url
Deliveryttl, priority (5 or 10), collapse_id, send_after
PlatformisIos, isAndroid, isAnyWeb
Androidandroid_channel_id, android_accent_color, android_sound, big_picture, large_icon, small_icon
iOSios_sound, ios_attachments, content_available, mutable_content
Identityname, external_id, idempotency_key
Channelchannel_for_external_user_ids (accepted: "push")

Semantics are preserved, including the quirky ones:

  • url / app_url / web_url become the tap destination, in that precedence;

  • platform flags follow exclusion logic: absent includes, explicit false excludes. isIos: true restricts nothing;

  • nobody reachable is not an error — it is a 200 with an empty id:

    { "errors": ["All included players are not subscribed"], "id": "", "recipients": 0 }
  • a partial send is also 200, with errors as an object:

    { "errors": { "invalid_player_ids": ["1111…"] }, "id": "0198…", "recipients": 1 }

Recognised segments: Subscribed Users, Total Subscriptions (an alias of the first), Active Users (seen in the last 7 days) and Engaged Users (receipt in the last 7 days).

The swap

# before
curl -X POST https://<previous-provider>/api/v1/notifications \
  -H "Authorization: Basic $OLD_KEY" ...

# after
curl -X POST https://api.pushmesh.io/api/v1/notifications \
  -H "Authorization: Basic $PUSHMESH_KEY" ...

The key looks like pm_live_<identifier>_<secret> and travels in Authorization: Basic. Bearer <key> and Basic with base64 of user:key are also accepted — whatever your HTTP client already does will work.

Differences worth testing before the cutover

BehaviourWhat to expect
Successful send200, always — not 201
errors inside a 200an array when nobody is reachable, an object on a partial send
Empty idmeans “nobody reachable” or a dry run — it is not a failure
Error textin Portuguese; branch on the HTTP status, never on the sentence
Content limit3,891 bytes on the rendered payload, already including 96 bytes the server injects
app_idrequired in the body/query, and must match the key

A typed deserialiser that pins errors to a list of strings breaks in exactly the most common migration scenario — a list carrying stale identifiers, which answers with errors as an object. Fix that before you switch.

What is refused on purpose

These fields return a named 400 rather than being silently accepted. Accepting and ignoring them would send the wrong message to your whole base without telling anyone:

FieldWhat to do
template_idmessage templates do not exist yet: build it in contents/headings
custom_datause data
existing_android_channel_iduse android_channel_id
filtersno equivalent today — resolve the audience on your side and send the list
excluded_segmentssame
buttonsaction buttons do not exist yet
delayed_option, delivery_time_of_dayper-user preferred-time scheduling does not exist yet; use send_after
throttle_rate_per_minutedelivery pacing belongs to the app, not to a single send
android_visibility, android_led_color, android_group, ios_categorynot configurable per send yet
include_aliases, include_subscription_idsthese belong to the newer user model; this compatibility layer is the player model

web_buttons is the single exception: accepted and ignored, because legacy integrations send an empty array.

Read that table before deciding. If your operation depends on filters or on message templates, migrating means rebuilding that on your side — better to find out now than the night before.


Part 2 — The app

React Native

npm install @pushmesh/sdk

The SDK ships a classic surface precisely so you do not have to rewrite the app’s calls:

import { ClassicApi } from '@pushmesh/sdk/compat';

await ClassicApi.initialize(APP_ID, { baseUrl: 'https://api.pushmesh.io' });

await ClassicApi.login('user-42');
await ClassicApi.Notifications.requestPermission();
await ClassicApi.User.addTags({ plan: 'pro' });
ClassicApi.InAppMessages.addTrigger('screen', 'checkout');

It covers initialize, login/logout, permission, notification listeners, user tags and in-app triggers. Where the names line up, the change is the import.

The SDK’s native API is the same thing under its own names:

import { PushMesh } from '@pushmesh/sdk';

await PushMesh.init({ appId: APP_ID, baseUrl: 'https://api.pushmesh.io' });
await PushMesh.login('user-42');

baseUrl is not optional in the version published today. The @pushmesh/sdk@0.7.2 package has a different host baked in as the default for when the field is omitted, and that host is not live: the device never registers and no server error appears, because the call never gets out. Pass baseUrl: 'https://api.pushmesh.io' on both paths — PushMesh.init and ClassicApi.initialize — until a new release fixes the default. It is the first thing to check if the new base does not grow after you publish the app.

What needs your attention in the app

  1. Remove the previous SDK. Two push SDKs fighting over the device token is the recipe for push disappearing with no error at all.
  2. Register your Firebase parameters in the dashboard (Android). The SDK fetches them at boot — the app stops needing the config file bundled in. And do not confuse them with the credential that actually sends: both are in Delivery credentials.
  3. Confirm token delivery in your refresh flow.
  4. Do not put the API key in the app. The routes the app uses require no credential for exactly this reason: a published APK is a published secret.

Part 3 — The device base (the part that is not magic)

There is no import-your-base button. There are two paths, and you will probably use both.

Path A — organic registration (the default, safe one)

You ship the app version carrying the new SDK. Every device that opens the app registers itself, and its player_id is born from that registration. The base grows at the speed of your version adoption.

  • Upside: zero risk, zero work, and the base that forms contains only live devices.
  • Cost: it takes as long as your update curve — typically weeks.
  • Identity continuity: send external_user_id at login and your usual identifier keeps working. That is what lets your own segmentation survive the switch.

Path B — bulk registration of tokens you already hold

If you keep your push token list, you can register them directly:

curl -X POST https://api.pushmesh.io/api/v1/players \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "'"$APP_ID"'",
    "device_type": 1,
    "identifier": "<the device push token>",
    "external_user_id": "user-42"
  }'

The route is idempotent per token: registering twice returns the same player_id.

Before relying on this, check that the tokens are still valid:

  • Android: the token is bound to the Firebase project. Keep the same project and the tokens keep working. Change projects and they do not — and there is no way to convert them.
  • iOS: the token is bound to the app and the notification environment, not to the provider. Keeping the same app and supplying the same authentication key, they keep working.

Also check the time cost, which tends to surprise: the registration route is capped at 120 requests per minute per source address, and an app is capped at 50,000 device registrations per hour. A large base, coming from a single server, takes days. If that is your situation, talk to us before you start rather than discovering it against a wall of 429s.

One note that prevents a wrong expectation: a token registered this way is only truly reachable once the device is running an app able to receive through PushMesh. Registering a token does not install the SDK.


Part 4 — Running both in parallel

The safest way to switch is not to switch all at once.

  1. Ship the app with the new SDK, keeping the previous provider live.

  2. Let adoption climb. Nothing changes in your sending yet.

  3. Measure the new base with the export, which returns who is alive:

    curl -X POST "https://api.pushmesh.io/api/v1/players/csv_export?app_id=$APP_ID" \
      -H "Authorization: Basic $PUSHMESH_KEY"
    {
      "csv_file_url": "https://…/exports/…/players-20260827T150000Z-….csv.gz",
      "pm_pronto_em_s_estimado": 30,
      "pm_validade_h": 72
    }

    The file is gzipped CSV with the columns id,external_user_id,notification_types,invalid_identifier,last_active. Fetch the URL: it returns 404 while the file is still being built and 200 once it is ready. The URL is the credential — the object is readable by anyone holding the address, and it carries your user identifiers. Do not paste it into a ticket or a chat.

  4. Send in parallel to a small slice, on both sides, and compare. Here you get an advantage the comparison usually reveals: alongside successful (the provider accepted it), PushMesh returns recebidos — the confirmation that came back from the device itself.

  5. Move the traffic once the new base covers what you need.

  6. Turn off the previous provider.


Checklist

Server

  • Base URL pointed at https://api.pushmesh.io
  • New key in the vault; no key in the repository
  • No field from the refused table in your payload
  • errors handled as a union of array and object
  • Branching on HTTP status, not on error text
  • Idempotency-Key on sends that must not duplicate

App

  • Previous SDK removed
  • @pushmesh/sdk installed and initialised at boot with baseUrl
  • Firebase parameters registered in the dashboard (Android)
  • external_user_id sent at login
  • No API key embedded in the app

Base

  • Path chosen (organic, bulk, or both)
  • If bulk: token validity confirmed and pacing agreed
  • Parallel period defined
  • Export reviewed before the cutover

Where to get help

If anything on this page does not match what you are seeing in practice, the page is what is wrong — and we want to know. Every API response carries the X-Pm-Request-Id header; quote that identifier and we will find the exact call.