Notifications
AppMint Push - Notify Your Users
Send notifications to everyone who installed your app, straight from AppMint on your own Firebase project - schedule, edit after sending, cancel, deep links
1How It Works#
AppMint Push sends notifications to everyone who installed your app, from inside AppMint - send now, schedule, edit after sending, cancel, deep links.
Every message can be designed: 8 templates, your own colour, up to 3 buttons that each open their own page, sound and pop-up choices, and a native in-app banner, card, sheet or full screen while the app is open. A live preview shows it exactly as users' phones will.
It runs on YOUR OWN Firebase project: your users, your project, no shared limits, and nothing of yours hosted by AppMint. Firebase Cloud Messaging is free with no message limit.
You need two files from that project - google-services.json (for the build) and a service-account key (for sending). The next steps get both. Works with every build mode: website, ZIP, HTML and AI.
AppMint Push and OneSignal are alternatives: turning one on turns the other off.
2Create a Firebase Project#
Go to Firebase Console and click 'Add project' (or open a project you already have). A free Firebase project is enough - no billing needed for notifications.
One project can hold several of your apps.
3Add Your App and Download google-services.json#
In your Firebase project:
- Click 'Add app' and choose Android
- Enter the package name EXACTLY as set in the AppMint wizard (e.g. com.yourname.yourapp) - a mismatch means notifications never arrive, and AppMint refuses the build for it
- Click 'Register app' and download google-services.json
- You can skip the remaining SDK steps - AppMint handles them
4Turn It On and Upload the File#
In the build wizard's Push Notifications card:
- Enable 'AppMint Push'
- Tap 'Upload google-services.json' and pick the file from the previous step
The wizard checks the file contains your package name. Then build your APK as normal - during the build your app is registered for push against your project.
Users just install the app and open it once. On Android 13+ they are asked to allow notifications after the first screen loads.
Templates, buttons and in-app styles need an app built with this version of AppMint or later. An older build still receives every message - it shows it in the plain style it always did.
5Upload Your Service-Account Key (Once)#
AppMint sends through your project, so it needs a key from it - the same file OneSignal asks for.
In Firebase Console:
- ⚙ Project settings → 'Service accounts' tab
- Click 'Generate new private key' and confirm - a JSON file downloads
In AppMint: Home > Notifications → pick your app → 'Upload key' → choose that JSON.
AppMint verifies the key with a test send to your project before storing it (encrypted), so a green tick means the next send will really go out. One upload per app; rebuilding the app does not need it again unless you switch Firebase projects.
⚠️ Upload the SERVICE-ACCOUNT key here, not google-services.json - the screen tells you if you mix them up. To revoke access later, delete the key in Firebase Console.
6Send Your First Notification#
In Home > Notifications:
- Pick your app
- Write a title (message, image and link are optional)
- Check the Live preview card
- Tap 'Send Notification'
Every phone with your app installed receives it.
For the image, tap the Image box: 'Choose from device' (up to 2 MB, PNG, JPEG or WebP) or 'Paste an image link' (https://). A picked image is uploaded only when you press Send - you can pick, replace or remove it as often as you like first.
7Pick a Template#
Design → Template sets the notification's layout. The text always comes first - a picture never covers the message:
- Automatic - Image card when the message has an image, otherwise Standard
- Standard - title and the whole message; an image becomes a small thumbnail beside it
- Image card - the picture BELOW the readable title and message
- Banner - a card filled with your colour, with an optional badge
- Promo - a badge such as '50% OFF' and a coloured button (your first button)
- List - up to 6 short lines, e.g. several order updates
- Countdown - a live timer that counts down to a date and time you pick, then leaves the tray by itself
- Progress - a progress bar; edit & resend with a new value and the bar moves on every phone without buzzing again
- Minimal - title and one line, no sound, no pop-up
Text and image sizes adapt to each user's font size, so nothing gets cut off on phones with large text.
8Choose a Colour#
Design → Colour tints the notification's icon, banner, buttons and countdown. Pick one of the 9 ready colours, or '+' for any #RRGGBB.
Every ready colour reads well on both light and dark phones. A custom colour is checked against both: if it is too pale or too dark, the note under the colours says so, and 'Fix contrast' in the custom-colour box nudges it to the nearest shade that works while keeping its hue. Text on a coloured button or banner is inked black or white automatically.
Use the 'Light phone' / 'Dark phone' tiles in the Live preview to see both.
9Add Buttons#
Design → Buttons: '+ Add button' (up to 3). Each has a label (up to 24 characters) and its own link - e.g. 'Track order' → #/orders/1042 and 'Help' → #/support. Leave a button's link empty to open the main link.
Buttons show under the notification in the tray and in the in-app presentation. In the Promo template the first button becomes the big coloured button on the card.
10While the App Is Open#
Design → 'While the app is open' decides what a user sees when the message arrives while they are using your app:
- App's own design (default) - your app's code shows it (see In-App Inbox below)
- Top banner - slides down from the top and hides itself after a few seconds
- Centre card - a card over the dimmed app with the image, text and buttons
- Bottom sheet - the same card rising from the bottom
- Full screen - a large image with the buttons at the bottom
The presentation follows the phone's light or dark theme, and Back or tapping outside closes it. When it is shown, the same message is not also put in the tray - the user is already looking at it.
With 'In-app only' (Delivery → Show as), a message that arrives while the app is closed is shown the next time the user opens it.
11Sound, Pop-up and Timing#
Design → Behaviour:
- Sound - off for a silent notification
- Pop up on screen - off to skip the peek over whatever the user is doing (it still lands in the tray)
- Auto-dismiss - removes the notification from the tray after 30 seconds up to 3 days (Android 8 and newer)
Advanced options:
- Icon / avatar image - a round picture beside the text (https:// link)
- Bundle key - messages sharing it are grouped under one heading
- Replace key - a newer message with the same key REPLACES the older one on the phone and in the inbox, e.g. a live score
Users can also tune sound per type in Android's notification settings for your app.
12Test, Save and Reuse#
- Live preview - Collapsed, Expanded and In-app tiles, on a light or dark phone, drawn by the same code your users' phones run
- 'Test on this phone' - shows the message on YOUR phone only, as a real notification or as the in-app presentation over the Notifications screen. Nothing is sent to users
- Saved templates (book icon at the top) - 'Save the current message as a template' keeps the text, link, image and every design choice on this phone; 'Use' loads it back in one tap
- In History, 'Duplicate as a new message' starts a new message from any earlier one
13Schedule for Later#
In the Delivery card, tap 'Schedule' under When, then pick a date and time. The notification goes out automatically at that moment.
Until it fires, you can edit or cancel it from the History list below.
14Fix a Mistake After Sending#
Tap any notification in History:
- 'Edit & resend' - loads it back into the composer with its whole design; 'Update & resend' then replaces it IN PLACE on every device
- 'Cancel' - removes it from every phone where it has not been opened yet, including an in-app presentation still on screen
- 'Delete' - same as cancel, and clears it from your history
⚠️ A notification the user already opened or dismissed cannot be taken back.
15Open a Specific Page#
The optional Link field controls what a tap opens (each button can have its own):
- A full https:// address (great for website apps)
- An in-app route like #/offers
Your app's code can also read it any time:
const link = window.WebToApk?.getLaunchUrl?.();
window.addEventListener('appmint:deep-link',
e => goTo(e.detail.url));16In-App Inbox (Optional)#
Every push is also stored inside the installed app (last 50), so your app can show its own notifications screen with unread badges. This works in EVERY build mode - including website (URL) apps, where you just add these few lines to your own site.
When the shell already showed a message on screen (a style under 'While the app is open'), the event says presented: true - do not draw a second banner then. An app that draws all its own banners can turn the shell's off with setPushPresentation('none').
Dismiss is a SWIPE, not a tiny ✕, and it is reversible - pair it with a brief Undo:
const B = window.WebToApk;
const inbox = JSON.parse(B?.getPushInbox?.() ?? "[]");
// [{id,title,body,imageUrl,link,receivedAt,read,
// template?,accent?,buttons?,badge?,inapp?...}]
const unread = inbox.filter(m => !m.read).length;
// live arrival while the app is open
window.addEventListener('appmint:push', e => {
const d = e.detail;
if (d.type === 'received' && !d.presented) showBanner(d.message);
refreshInbox(); // fires for 'revoked' too
});
B?.markPushRead?.(id); // "" = mark all read
B?.dismissPushMessage?.(id); // swipe away
B?.restorePushMessage?.(id); // ...Undo
B?.setPushPresentation?.('none'); // app draws its own17If Nothing Arrives on Your Test Phone#
Almost always a limit on the TEST PHONE, not your app or your users.
Android allows roughly 100 push registrations per phone. Every FRESH install of an app that uses notifications takes one - so installing build after build while testing can use them all up, and that phone then stops receiving for newly installed apps.
Free up slots (this is what actually works):
- Uninstall test apps you no longer need - Android releases the slot each one was holding
- Or clear a test app's data: Settings > Apps > [that app] > Storage > Clear data - that releases its slot without uninstalling
- On an emulator, wiping it resets every slot at once
Restarting the phone does NOT help: these registrations are stored permanently, which is why they survive a reboot. Clearing Google Play Services' cache does not free them either.
Avoid it while testing:
- Keep ONE package name while you iterate - every different package name takes its own slot
- Install the new APK OVER the old one instead of uninstalling first - an update keeps the existing registration, a reinstall spends a new one
- Test on a spare phone or an emulator; wiping an emulator resets its slots instantly
- Delete test apps you have finished with
Also check the basics: the app was opened at least once after installing, and notifications are allowed for it in Android settings. The app re-subscribes on every launch, so it recovers by itself once slots free up - no rebuild needed.
18Limits and Good Practice#
- No limit on how many notifications you send - Firebase Cloud Messaging is free
- Title up to 120 characters, message up to 1000; image and icon links must be https://
- Up to 3 buttons (24-character labels) and up to 6 list lines
- On the free plan a short ad plays before each send
- Test apps: remove them with the trash icon next to the app selector - this deletes the history and stored key in AppMint only; your Firebase project is untouched, and a rebuild adds the app back
- Rebuilding with a NEW google-services.json from the SAME project changes nothing: your key stays, existing installs keep receiving
- ⚠️ Moving the app to a DIFFERENT Firebase project is a clean break. Rebuild with the new google-services.json, upload the new project's key (AppMint drops the old one, since it could never send for the new project) - and know that phones still running the OLD version stop receiving until their users update. Notifications reach a project, and those installs are still listening to the old one
- Send sparingly: users uninstall apps that spam them
19Choose Who Gets It#
Under 'Who gets it', a message goes to Everyone (the default) or to the phones you choose:
- Countries, languages, Android versions and app versions
- Activity - opened the app in the last N days, or has not opened it for N days
- Only phones where notifications are turned on
- User IDs and tags that your app's page sets (next step)
Before you send, the screen shows how many phones your choice reaches.
More ways to send:
- Each phone's local time (under When) - e.g. 09:00 on every phone's own clock, time zone by time zone
- A/B test - write a version B; each version goes to a test share of the audience, and after the wait you set, the one with more opens (or taps) goes to everyone else
- Daily limit per phone - the most messages one phone gets from this app in a day; messages over it are skipped for that phone and shown as 'skipped (daily limit)'
History shows, for each message, how many phones it went to, how many failed and how many were skipped.
⚠️ Choosing who, local time, A/B tests, automations and the daily limit reach only phones running an app built from 30 Sep 2026 on (they register themselves). Phones on older builds still get 'Everyone' sends - rebuild and publish an update to reach them this way too.
20Target Signed-In Users and Tags#
Your app's page tells AppMint Push who is signed in and what to know about this install. Then you can send to one user, or to everyone with a tag such as plan = pro.
- setPushUser - after sign-in, with YOUR OWN account id (the database id, never an email address or phone number). Letters, digits and _ @ . : + - (up to 128). Call it with "" on sign-out, or the phone keeps getting that user's messages
- setPushTag / setPushTags - a tag name (letters, digits and _, up to 32) and a value (up to 128). "" removes a tag; up to 50 per install
- getPushUser / getPushTags - read back what this phone holds
IDs and tags belong to the install, not to the account: clear user-specific tags on sign-out too. The phone reports a change a few seconds later (on the next launch when it is offline). The same user signed in on two phones is reached on both.
const B = window.WebToApk;
// after sign-in: your own account id
B?.setPushUser?.(user.id);
B?.setPushTag?.('plan', user.isPro ? 'pro' : 'free');
B?.setPushTags?.(JSON.stringify({ city: 'pune', level: '12' }));
// on sign-out
B?.setPushUser?.('');
B?.setPushTag?.('plan', '');
// read back
const who = B?.getPushUser?.() ?? '';
const tags = JSON.parse(B?.getPushTags?.() ?? '{}');21Automations#
Messages that go out by themselves. In Automations, tap '+ New' and pick one:
- Welcome - N minutes after a new install (from 0 minutes to 30 days). Each phone gets it once
- Win-back - when someone has not opened the app for N days (1 to 180). Once per absence: a phone that comes back and stays away again gets it again
- Tag - N minutes after your app's page sets a tag, optionally only for one value. It goes out only if the tag is still set when the time is up
Abandoned cart: set a tag when items go into the cart and remove it at checkout. A 'cart' automation with a 60-minute delay then reminds only the people who left without buying. Setting the same value again does not restart the clock; after the tag is removed, setting it again can send once more.
Automations count towards the daily limit per phone, and reach phones on builds made from 30 Sep 2026 on.
const B = window.WebToApk;
// items added to the cart
B?.setPushTag?.('cart', 'open');
// checkout finished, or the cart emptied: no reminder
B?.setPushTag?.('cart', '');22Reach Users With Notifications Off#
A phone where notifications are turned off never shows a tray notification - but your message still reaches the app, and an in-app message appears the next time the user opens it. Use that to ask them to turn notifications back on:
- Who gets it → Choose who → turn on 'Notifications off'
- Show as → In-app only (a Top banner or Centre card under Design)
- Design → Buttons → '+ “Turn on notifications” button'. Its link is appmint://notifications/enable: a tap shows the permission prompt, or opens the app's notification settings when the prompt can no longer be shown
When the app has phones with notifications off, the send center shows 'Reach the N phones with notifications off' under the Firebase key - one tap sets all three. As soon as a user turns notifications on, their phone leaves that audience.
The button needs an app built with this version of AppMint or later; older builds treat the link as an ordinary link. appmint://notifications/settings opens the settings screen directly.
// Your page can offer the same thing itself:
const r = await window.AppMint?.enableNotifications?.();
// r = { state: 'granted' | 'prompt' | 'blocked' | 'unavailable',
// changed, openedSettings }
// and hear a push button's result too:
window.addEventListener('appmint:notifications', e => {
if (e.detail.state === 'granted') hideNotifBanner();
});23In-App Messages#
In-app messages are campaigns you make in the send center: Push Notifications → 'In-app messages' → '+ New'. The app downloads them and shows them itself, with no push involved, when the trigger you picked happens:
- App opens - the Nth app open
- A page or route (/checkout, #/cart, /product/*)
- Time in app - N seconds in the app
- An event your page tracks (next step)
- A push tag
- Notifications turned off - pair it with a 'Turn on notifications' button
- App update - the first open after an update
Show it as a banner, modal, sheet or fullscreen. Each message has a frequency (once per install by default), an optional audience and an optional start and end time. It needs AppMint Push in the build.
24Tell the App What Happened#
Your page's part is small: report the moments that matter with trackEvent. A message whose trigger is 'When an event happens' with the same name shows now (or after the wait you picked). Names: letters, digits and _ . : - (up to 64). Events are not counted or stored anywhere; they only decide whether a message shows.
Route messages follow real URL changes (page loads, pushState, #hash routes) by themselves. A view the address does not show (a tab, a wizard step) can be reported as a route with the appmint:route event.
if (window.AppMint && AppMint.trackEvent) AppMint.trackEvent('checkout_done', { total: 42 });
// raw bridge: props as a JSON string ('' for none)
WebToApk.trackEvent('level:3', '');
// a view the address does not show, reported as a route
window.dispatchEvent(new CustomEvent('appmint:route', { detail: { path: '/cart' } }));25Draw Messages Yourself (Optional)#
Every message raises appmint:inapp with { type: 'shown' | 'clicked' | 'dismissed', id, name } (and calls window.onAppMintInApp(detail) if you define it).
To draw messages yourself, call WebToApk.setInAppPresentation('none'): the app then draws nothing and the 'shown' event carries presented: false and the message (title, body, image, buttons). setInAppPresentation('auto') hands drawing back to the app. WebToApk.getInAppCampaigns() lists what this install holds ([{id, name}]).
All of it needs AppMint Push in the build; in any other build the methods answer false / "[]".
window.addEventListener('appmint:inapp', e => {
// e.detail = { type: 'shown' | 'clicked' | 'dismissed', id, name, presented?, message? }
if (e.detail.type === 'shown' && e.detail.presented === false) drawMyBanner(e.detail.message);
});
WebToApk.setInAppPresentation('none'); // 'auto' gives drawing back to the app
const held = JSON.parse(WebToApk.getInAppCampaigns() || '[]'); // [{id, name}]26Start From a Template#
'+ New' opens a gallery of ready-made messages, each a live preview you can search or filter by layout: Welcome, Rate us, Turn on notifications, What's new, Flash sale (with a live countdown), Seasonal promo, New feature, Update available, Abandoned cart, Win back, Star survey, NPS score, Invite friends, Subscription offer, Streak, Tip, Permission explainer, Maintenance (stays until closed), Terms update (no close button) and Follow us. A template fills in the layout, emoji or picture, text, buttons, trigger and frequency - change anything before saving. 'Blank message' starts empty.
Star survey, Rate us and NPS ask a question: a 1-5 star row or 0-10 score. The first button waits for an answer; the answer is reported once, when the message closes, as an appmint:inapp 'answered' event { value, question }, and the send center shows the average stars or the NPS score with the spread.
Button links can also be appmint://review (Google Play's review sheet), share:text (the share sheet), copy:text (the clipboard), route:/page and event:name. Text can use {app}, {store}, {name}, {streak} and {tag:key}, filled in on each phone; {name|friend} gives a word for when there is no value. A message whose placeholder has no value is not shown. Needs an app built with this version or later.
window.addEventListener('appmint:inapp', e => {
const d = e.detail;
if (d.type === 'answered') {
// d.question: 'stars' (1-5) or 'nps' (0-10); d.template e.g. 'survey_stars'
if (d.question === 'nps' && d.value <= 6) openFeedbackForm();
}
if (d.type === 'clicked' && d.link === 'event:allow') askForLocation();
});
// {streak} and {tag:key} come from push tags
WebToApk.setPushTag('streak', '7');