# The push inbox and audience - JavaScript bridge API

> Messages the creator sent from a dashboard, kept on the device so the page can show a history rather than only a tray pop-up - and the signed-in user id and tags that let the send center target one user or a tag, and fire automations such as an abandoned-cart reminder.

- **Applies to:** Appwright
- **Source:** extracted from the app runtime and its per-method example files; this page is generated from them.
- **HTML:** https://freewebsitetoapk.com/docs/api/push

### `clearPushInbox`

```js
window.WebToApk.clearPushInbox(): Boolean
```

Empties the push inbox (the tray and server history are untouched).

**Example**

Empties the push inbox on this phone.

**Returns:** `true` when done; `false` on an internal error. **Needs:** turn on **Appwright Push** in the Integrate step when you build.

```js
document.getElementById('clear-inbox').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.clearPushInbox)) return;
  if (!confirm('Delete all messages? This cannot be undone.')) return;
  if (WebToApk.clearPushInbox()) renderInbox();
});
```

Empty the inbox when the user signs out, so the next person on this phone does not see their messages:

```js
async function signOut() {
  await myAuth.signOut();
  if (window.WebToApk && window.WebToApk.clearPushInbox) {
    WebToApk.clearPushInbox();
  }
  document.getElementById('bell-count').textContent = '';
  showLoginScreen();
}
```

Show a settings row with how many messages are stored, disabled when there is nothing to delete:

```js
function paintClearRow() {
  const btn = document.getElementById('clear-inbox');
  if (!(window.WebToApk && window.WebToApk.clearPushInbox)) { btn.hidden = true; return; }
  const count = JSON.parse(WebToApk.getPushInbox() || '[]').length;
  btn.textContent = count ? 'Delete ' + count + ' messages' : 'No messages';
  btn.disabled = count === 0;
}

document.getElementById('clear-inbox').addEventListener('click', function () {
  if (WebToApk.clearPushInbox()) paintClearRow();
  else alert('Could not delete the messages. Please try again.');
});

paintClearRow();
```

**Notes:** this is permanent - unlike `dismissPushMessage`, there is no undo. Tray notifications and your send history in Appwright are not touched. New messages still arrive.

### `dismissPushMessage`

```js
window.WebToApk.dismissPushMessage(id: String): Boolean
```

Dismisses ONE message from the inbox - the per-row swipe-away an inbox screen needs (clearPushInbox is all-or-nothing). REVERSIBLE by design: the entry is hidden, not destroyed, so the UI can offer "Undo" for a few seconds after the swipe. Local only: the tray notification and the creator's history are untouched.

**Example**

Hides one message from the push inbox (a swipe-away). It can be undone with `restorePushMessage(id)`.

**Returns:** `true` if the message was hidden; `false` if the id is unknown or it was already hidden. **Needs:** turn on **Appwright Push** in the Integrate step when you build.

**Swipe away with a 5-second Undo:**

```js
function dismissRow(id, rowEl) {
  if (!(window.WebToApk && window.WebToApk.dismissPushMessage)) return;
  if (!WebToApk.dismissPushMessage(id)) return;
  rowEl.remove();

  const bar = document.getElementById('undo-bar');
  bar.hidden = false;
  const timer = setTimeout(function () { bar.hidden = true; }, 5000);
  document.getElementById('undo-btn').onclick = function () {
    clearTimeout(timer);
    bar.hidden = true;
    WebToApk.restorePushMessage(id);         // back in its old place
    renderInbox();
  };
}
```

Tidy the inbox when it opens by hiding messages that are read and older than 30 days:

```js
function hideOldReadMessages() {
  if (!(window.WebToApk && window.WebToApk.dismissPushMessage)) return 0;
  const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
  let hidden = 0;
  JSON.parse(WebToApk.getPushInbox() || '[]').forEach(function (m) {
    if (m.read && m.receivedAt < cutoff && WebToApk.dismissPushMessage(m.id)) hidden++;
  });
  return hidden;
}

if (hideOldReadMessages() > 0) renderInbox();
```

Dismiss a row with a real finger swipe (drag it more than 40% of its width):

```js
function makeSwipeable(rowEl, id) {
  let startX = null;
  rowEl.addEventListener('pointerdown', function (e) { startX = e.clientX; rowEl.setPointerCapture(e.pointerId); });
  rowEl.addEventListener('pointermove', function (e) {
    if (startX !== null) rowEl.style.transform = 'translateX(' + (e.clientX - startX) + 'px)';
  });
  rowEl.addEventListener('pointerup', function (e) {
    const dx = startX === null ? 0 : e.clientX - startX;
    startX = null;
    if (Math.abs(dx) > rowEl.offsetWidth * 0.4 && window.WebToApk && WebToApk.dismissPushMessage(id)) {
      rowEl.remove();
    } else {
      rowEl.style.transform = '';            // not far enough: snap back
    }
  });
}
```

**Notes:** only this phone's inbox changes. The tray notification and your send history in Appwright stay. Hidden messages are not returned by `getPushInbox()`.

### `getPushInbox`

```js
window.WebToApk.getPushInbox(): String
```

Appwright Push inbox (creator-sent messages, newest first, max 50): JSON array of {id, title, body, imageUrl, link, receivedAt, read}. Always the full current truth - an app that mounts late loses nothing by having missed the live `appmint:push` events.

**Example**

Reads the in-app inbox of push messages you sent with Appwright Push: newest first, up to 50, without the ones the user dismissed.

**Returns:** a JSON **string**, synchronously. Parse it. Each item: `{ id, title, body, imageUrl, link, receivedAt, read }` (`receivedAt` in epoch ms), plus optional design fields when you used a template in Appwright: `template`, `accent`, `iconUrl`, `buttons: [{label, link}]`, `badge`, `inapp`, `countdownAt`, `progress`, `lines`, `collapse`. `'[]'` when empty. **Needs:** turn on **Appwright Push** in the Integrate step when you build; without it the inbox is always empty.

**Render the inbox, and refresh it live with `appmint:push`:**

```js
function loadInbox() {
  if (!(window.WebToApk && window.WebToApk.getPushInbox)) return [];
  try { return JSON.parse(WebToApk.getPushInbox() || '[]'); } catch (e) { return []; }
}

function renderInbox() {
  const inbox = loadInbox();
  const unread = inbox.filter(function (m) { return !m.read; }).length;
  document.getElementById('bell-count').textContent = unread ? String(unread) : '';
  const ul = document.getElementById('inbox');
  ul.innerHTML = '';
  inbox.forEach(function (m) {
    const li = document.createElement('li');
    li.className = m.read ? 'read' : 'unread';
    li.textContent = m.title + ' — ' + m.body;
    li.onclick = function () {
      WebToApk.markPushRead(m.id);
      if (m.link) location.href = m.link;
      renderInbox();
    };
    ul.appendChild(li);
  });
}

// While the app is open:
window.addEventListener('appmint:push', function (e) {
  // { type: 'received', message: {id, title, body, ...}, presented: true|false }
  // { type: 'revoked', id }  — you retracted the message
  // presented = the app already showed it on screen (see setPushPresentation)
  if (e.detail.type === 'received' && !e.detail.presented) showBanner(e.detail.message.title);
  renderInbox();
});

renderInbox();
```

Update the unread count on your bell whenever the user comes back to the app (messages that arrived while it was in the background are already in the inbox):

```js
function unreadCount() {
  if (!(window.WebToApk && window.WebToApk.getPushInbox)) return 0;
  try {
    return JSON.parse(WebToApk.getPushInbox() || '[]').filter(function (m) { return !m.read; }).length;
  } catch (e) { return 0; }
}

function paintBell() {
  const n = unreadCount();
  const badge = document.getElementById('bell-count');
  badge.textContent = n > 9 ? '9+' : (n ? String(n) : '');
  badge.hidden = n === 0;
}

document.addEventListener('visibilitychange', function () {
  if (document.visibilityState === 'visible') paintBell();
});
paintBell();
```

Show a message's picture and its buttons (only when you designed it with them), with the time it arrived:

```js
function renderCard(m) {
  const card = document.createElement('article');
  if (m.accent) card.style.borderLeft = '4px solid ' + m.accent;
  if (m.imageUrl) {
    const img = document.createElement('img');
    img.src = m.imageUrl; img.alt = '';
    card.appendChild(img);
  }
  const h = document.createElement('h3');
  h.textContent = m.title;
  const p = document.createElement('p');
  p.textContent = m.body;
  const time = document.createElement('time');
  time.textContent = new Date(m.receivedAt).toLocaleString();
  card.append(h, p, time);
  (m.buttons || []).forEach(function (b) {
    const btn = document.createElement('button');
    btn.textContent = b.label;
    btn.onclick = function () { WebToApk.markPushRead(m.id); if (b.link) location.href = b.link; };
    card.appendChild(btn);
  });
  return card;
}

if (window.WebToApk && window.WebToApk.getPushInbox) {
  const list = document.getElementById('inbox');
  JSON.parse(WebToApk.getPushInbox() || '[]').forEach(function (m) { list.appendChild(renderCard(m)); });
}
```

**Notes:** the inbox is kept on the phone, so read it when your screen opens - events only arrive while the app is on screen. A message you retract from Appwright disappears from the inbox and the tray. Messages sent as "push only" (tray) are not added to the inbox. The same detail is also passed to `window.onAppMintPush(detail)` if you define it.

### `getPushTags`

```js
window.WebToApk.getPushTags(): String
```

This install's tags as a JSON object string, e.g. {"plan":"pro"}.

**Example**

Reads this install's Appwright Push tags.

**Returns:** a JSON **string**, synchronously - an object of text values, e.g. `'{"plan":"pro","cart":"open"}'`; `'{}'` when there are none. Parse it. **Needs:** turn on **Appwright Push** in the Integrate step when you build.

**Read one tag:**

```js
function pushTags() {
  if (!(window.WebToApk && window.WebToApk.getPushTags)) return {};
  try { return JSON.parse(WebToApk.getPushTags() || '{}'); } catch (e) { return {}; }
}

const plan = pushTags().plan || 'free';
```

**Set a tag only when it changes** - every `setPushTag` call makes the install check in with Appwright Push, so skip the ones that change nothing:

```js
function ensureTag(key, value) {
  if (!(window.WebToApk && window.WebToApk.getPushTags)) return;
  const tags = JSON.parse(WebToApk.getPushTags() || '{}');
  if ((tags[key] || '') !== value) WebToApk.setPushTag(key, value);
}

ensureTag('plan', user.isPro ? 'pro' : 'free');
```

**List them on a debug screen** while you test sending to a tag:

```js
const list = document.getElementById('push-tags');
if (window.WebToApk && window.WebToApk.getPushTags) {
  const tags = JSON.parse(WebToApk.getPushTags() || '{}');
  list.textContent = Object.keys(tags).length
    ? Object.keys(tags).map(function (k) { return k + ' = ' + tags[k]; }).join('\n')
    : 'No tags yet';
}
```

**Notes:** this is what the phone holds; the send center sees it after the install's next check-in, a few seconds after a change when the phone is online. Android only: the iOS app has no `WebToApk.getPushTags`, so the feature check skips it there.

### `getPushUser`

```js
window.WebToApk.getPushUser(): String
```

The user id last set with setPushUser ("" when signed out or never set).

**Example**

Reads the user id this install last gave Appwright Push with `setPushUser`.

**Returns:** a `string`, synchronously - the saved id, or `''` when nobody is signed in or it was never set. **Needs:** turn on **Appwright Push** in the Integrate step when you build.

**Set the id only when it changed** (on every app start):

```js
function syncPushUser(currentUserId) {
  if (!(window.WebToApk && window.WebToApk.getPushUser)) return;
  const want = currentUserId || '';
  if (WebToApk.getPushUser() !== want) WebToApk.setPushUser(want);
}

syncPushUser(localStorage.getItem('userId'));
```

**Clear a stale id** when your own session expired while the app was closed:

```js
async function checkSession() {
  const session = await myBackend.currentSession();       // null when expired
  if (!session && window.WebToApk && window.WebToApk.getPushUser && WebToApk.getPushUser() !== '') {
    WebToApk.setPushUser('');                              // stop personal messages to this phone
  }
}

checkSession();
```

**Show it on a debug screen** while you test sending to one user:

```js
const info = document.getElementById('push-debug');
if (window.WebToApk && window.WebToApk.getPushUser) {
  const id = WebToApk.getPushUser();
  info.textContent = id ? 'Push user: ' + id : 'Push user: nobody signed in';
} else {
  info.textContent = 'Not inside the app';
}
```

**Notes:** this is what the phone holds. The send center sees a change after the install's next check-in, a few seconds later when the phone is online. Android only: the iOS app has no `WebToApk.getPushUser`, so the feature check skips it there.

### `markPushRead`

```js
window.WebToApk.markPushRead(id: String): Boolean
```

Marks one push message read; pass "" to mark every message read.

**Example**

Marks one push inbox message as read, or all of them when you pass `''`.

**Returns:** `true` if a message changed; `false` if nothing changed (unknown id, already read, or Appwright Push not enabled). **Needs:** turn on **Appwright Push** in the Integrate step when you build.

```js
function openMessage(id) {
  if (!(window.WebToApk && window.WebToApk.markPushRead)) return;
  WebToApk.markPushRead(id);                 // one message
  updateBellCount();
}

document.getElementById('mark-all-read').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.markPushRead)) return;
  WebToApk.markPushRead('');                 // every message
  updateBellCount();
});

function updateBellCount() {
  const inbox = JSON.parse(WebToApk.getPushInbox() || '[]');
  const unread = inbox.filter(function (m) { return !m.read; }).length;
  document.getElementById('bell-count').textContent = unread ? String(unread) : '';
}
```

Mark a message read once the user has actually seen it on screen, not just when they tap it:

```js
if (window.WebToApk && window.WebToApk.markPushRead) {
  const seen = new IntersectionObserver(function (entries) {
    entries.forEach(function (entry) {
      if (!entry.isIntersecting) return;
      const id = entry.target.dataset.msgId;
      seen.unobserve(entry.target);
      if (WebToApk.markPushRead(id)) {       // true only when it was unread
        entry.target.classList.replace('unread', 'read');
        updateBellCount();
      }
    });
  }, { threshold: 0.6 });

  document.querySelectorAll('#inbox li.unread').forEach(function (li) { seen.observe(li); });
}
```

Clear the badge when the inbox screen opens, and only redraw when something was actually unread:

```js
function openInboxScreen() {
  showScreen('inbox');
  if (!(window.WebToApk && window.WebToApk.markPushRead)) return;
  const changed = WebToApk.markPushRead('');  // false: nothing was unread
  if (changed) {
    document.getElementById('bell-count').textContent = '';
    renderInbox();
  }
}
```

**Notes:** read state is stored only on this phone. It does not remove the tray notification.

### `restorePushMessage`

```js
window.WebToApk.restorePushMessage(id: String): Boolean
```

Undo a dismiss: puts the message back in its place in the inbox.

**Example**

Undoes `dismissPushMessage(id)`: the message comes back to the push inbox in its old place.

**Returns:** `true` if the message was restored; `false` if the id is unknown or it was not hidden. **Needs:** turn on **Appwright Push** in the Integrate step when you build.

```js
let lastDismissed = null;

function onSwipe(id) {
  if (!(window.WebToApk && window.WebToApk.dismissPushMessage)) return;
  if (WebToApk.dismissPushMessage(id)) {
    lastDismissed = id;
    showUndo();
    renderInbox();
  }
}

document.getElementById('undo-btn').addEventListener('click', function () {
  if (!lastDismissed || !window.WebToApk) return;
  if (WebToApk.restorePushMessage(lastDismissed)) renderInbox();
  lastDismissed = null;
});
```

Undo several swipes at once: remember every message hidden on this screen and bring them all back:

```js
const hiddenThisVisit = [];

function hide(id) {
  if (window.WebToApk && WebToApk.dismissPushMessage(id)) {
    hiddenThisVisit.push(id);
    document.getElementById('undo-all').hidden = false;
    renderInbox();
  }
}

document.getElementById('undo-all').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.restorePushMessage)) return;
  hiddenThisVisit.splice(0).forEach(function (id) { WebToApk.restorePushMessage(id); });
  this.hidden = true;
  renderInbox();
});
```

Offer a "Recently hidden" list the user can restore from later. `getPushInbox()` does not return hidden messages, so keep their ids and titles yourself:

```js
function loadHidden() {
  try { return JSON.parse(localStorage.getItem('hiddenMsgs') || '[]'); } catch (e) { return []; }
}

function renderHidden() {
  const ul = document.getElementById('recently-hidden');
  ul.innerHTML = '';
  loadHidden().forEach(function (h) {
    const li = document.createElement('li');
    li.textContent = h.title + ' ';
    const btn = document.createElement('button');
    btn.textContent = 'Restore';
    btn.onclick = function () {
      WebToApk.restorePushMessage(h.id);     // false: retracted, cleared or already back
      const rest = loadHidden().filter(function (x) { return x.id !== h.id; });
      try { localStorage.setItem('hiddenMsgs', JSON.stringify(rest)); } catch (e) {}
      renderHidden();
      renderInbox();
    };
    li.appendChild(btn);
    ul.appendChild(li);
  });
}
```

**Notes:** a message cleared with `clearPushInbox()` or retracted by you from Appwright cannot be restored.

### `setPushPresentation`

```js
window.WebToApk.setPushPresentation(mode: String): Boolean
```

Native in-app presentation of push messages: "auto" (default) lets the shell draw the banner / modal / sheet / full-screen card the creator chose while the app is open; "none" turns it off for good (persisted) - for apps that draw their own. The `appmint:push` event and the inbox are unaffected either way; the received event's `presented` flag says whether the shell showed it.

**Example**

Turns the app's own on-screen display of Appwright Push messages on (`'auto'`, the default) or off (`'none'`) - for apps that draw their own banner from the `appmint:push` event.

**Returns:** `true` when the mode was saved; `false` for any value other than `'auto'` or `'none'`. The choice is saved on the phone and stays until changed. **Needs:** turn on **Appwright Push** in the Integrate step when you build.

**Draw your own banner** instead of the shell's:

```js
if (window.WebToApk && window.WebToApk.setPushPresentation) {
  WebToApk.setPushPresentation('none');       // 'auto' to give it back to the shell
}

window.addEventListener('appmint:push', function (e) {
  const d = e.detail;
  // { type: 'received', message: {...}, presented: false } — presented = the shell already showed it
  if (d.type === 'received' && !d.presented) showMyBanner(d.message.title, d.message.body, d.message.link);
  if (d.type === 'revoked') hideMyBanner(d.id);
});
```

Let the user switch pop-ups off in your settings screen. There is no getter, so keep your own copy of the choice to draw the switch:

```js
const toggle = document.getElementById('show-popups');
let saved = null;
try { saved = localStorage.getItem('pushPopups'); } catch (e) {}
toggle.checked = saved !== 'none';

toggle.addEventListener('change', function () {
  if (!(window.WebToApk && window.WebToApk.setPushPresentation)) return;
  const mode = toggle.checked ? 'auto' : 'none';
  if (WebToApk.setPushPresentation(mode)) {
    try { localStorage.setItem('pushPopups', mode); } catch (e) {}
  }
});
```

With `'auto'`, find out what the user did with a message the app showed on screen: `opened` when they tapped it (`action` is `-1` for the message itself, else the button index), `closed` when they dismissed it without tapping:

```js
window.addEventListener('appmint:push', function (e) {
  const d = e.detail;
  if (d.type === 'opened') {
    // the message is already marked read in the inbox
    track('push_opened', { id: d.id, button: d.action, link: d.link });
    updateBellCount();
  } else if (d.type === 'closed') {
    track('push_closed', { id: d.id });
  }
});
```

**Notes:** with `'auto'`, a message you sent with an in-app style is shown over the app while it is open (and then not also put in the tray). With `'none'` the shell never draws it; the inbox (`getPushInbox`) and the `appmint:push` event work the same either way. Setting `'none'` also closes a message the shell is showing now.

### `setPushTag`

```js
window.WebToApk.setPushTag(key: String, value: String): Boolean
```

Sets one Appwright Push tag (key: letters, digits, _ up to 32; value up to 128) - the send center can target "plan = pro", and an automation can fire a set time after a tag is set (an abandoned cart: set cart=open, clear it at checkout). An empty value removes the tag. Up to 50 tags per install.

**Example**

Sets one Appwright Push tag on this install, such as `plan = pro` or `city = pune`. The send center (Home > Notifications) can send to everyone with a tag, and a tag automation can send a message a set time after a tag is set - an abandoned cart, a finished level. An empty value removes the tag.

**Returns:** `true` when saved; `false` when the key is not 1-32 letters, digits or `_`, the value is longer than 128 characters, or the install would have more than 50 tags (nothing is saved then). Tags are kept on the phone and reported to Appwright Push a moment later (or on the next launch when offline). **Needs:** turn on **Appwright Push** in the Integrate step when you build. Tag targeting and automations reach phones running an app built with Appwright from 30 Sep 2026 on.

**Tag what the user bought or chose**, so you can send to "plan = pro" only:

```js
function pushTag(key, value) {
  if (!(window.WebToApk && window.WebToApk.setPushTag)) return false;
  return WebToApk.setPushTag(key, value);
}

pushTag('plan', user.isPro ? 'pro' : 'free');
pushTag('city', user.city.toLowerCase());
```

**Abandoned cart:** set a tag when items are in the cart and clear it at checkout. In the send center, add a tag automation on `cart` with a delay (say 60 minutes); a phone gets the reminder only if `cart` is still set when the time is up:

```js
function onCartChanged(items) {
  if (!(window.WebToApk && window.WebToApk.setPushTag)) return;
  // Setting 'open' again while it is already 'open' does not restart the automation's clock.
  WebToApk.setPushTag('cart', items.length > 0 ? 'open' : '');
}

function onCheckoutComplete() {
  if (window.WebToApk && window.WebToApk.setPushTag) WebToApk.setPushTag('cart', '');   // bought: no reminder
}
```

**Clear user-specific tags on sign-out**, together with the user id:

```js
function signOutOfPush() {
  if (!(window.WebToApk && window.WebToApk.setPushUser)) return;
  WebToApk.setPushUser('');
  WebToApk.setPushTag('plan', '');
  WebToApk.setPushTag('cart', '');
}
```

**Notes:** tags belong to this install, not to the signed-in user. Setting a tag to the value it already has changes nothing on the server. After a tag is removed, setting it again lets a tag automation send once more. Never put an email address, phone number or anything private in a tag. Android only: the iOS app has no `WebToApk.setPushTag`, so the feature check skips it there.

### `setPushTags`

```js
window.WebToApk.setPushTags(json: String): Boolean
```

Sets several tags at once from a JSON object string; "" values remove.

**Example**

Sets several Appwright Push tags at once from a JSON object string, e.g. `'{"plan":"pro","team":"red"}'`. A `""` value removes that tag; tags you do not name are kept.

**Returns:** `true` when saved; `false` when the string is not a JSON object, any key is not 1-32 letters, digits or `_`, any value is longer than 128 characters, or the install would end up with more than 50 tags - then nothing at all is saved. **Needs:** turn on **Appwright Push** in the Integrate step when you build. Tag targeting and automations reach phones running an app built with Appwright from 30 Sep 2026 on.

**After sign-in, tag the profile in one call:**

```js
function tagProfile(profile) {
  if (!(window.WebToApk && window.WebToApk.setPushTags)) return false;
  return WebToApk.setPushTags(JSON.stringify({
    plan: profile.plan,                   // 'free' | 'pro'
    level: String(profile.level),         // values are text: turn numbers into strings
    team: profile.team || ''              // '' removes the tag
  }));
}
```

**On sign-out, remove them all** (read the current tags, send each back as `''`):

```js
function clearPushTags() {
  if (!(window.WebToApk && window.WebToApk.getPushTags)) return;
  const tags = JSON.parse(WebToApk.getPushTags() || '{}');
  Object.keys(tags).forEach(function (k) { tags[k] = ''; });
  WebToApk.setPushTags(JSON.stringify(tags));
  WebToApk.setPushUser('');
}
```

**Finished a level:** tag it, and let a tag automation on `level_done` (say 10 minutes) send the next challenge:

```js
function onLevelComplete(level, score) {
  if (!(window.WebToApk && window.WebToApk.setPushTags)) return;
  WebToApk.setPushTags(JSON.stringify({
    level_done: String(level),            // a new value each level, so the automation can fire again
    last_score: String(score)
  }));
}
```

**Notes:** one call is one change: all of it is saved or none of it. The install reports the new tags to Appwright Push a moment later (or on the next launch when offline). Android only: the iOS app has no `WebToApk.setPushTags`, so the feature check skips it there.

### `setPushUser`

```js
window.WebToApk.setPushUser(id: String): Boolean
```

Appwright Push audience: tells the creator's send center WHO this install is, so a message can go to one signed-in user ("u_42") or to everyone with a tag. Call it after sign-in with your own stable account id; pass "" on sign-out. Letters, digits and _ @ . : + - (up to 128). Returns false for anything else. Never pass an email address or phone number - use your database id. Android installs only.

**Example**

Tells Appwright Push which of YOUR users is signed in on this phone, so the send center (Home > Notifications) can send a message to that one user. Call it after sign-in with your own stable account id; call it with `''` on sign-out.

**Returns:** `true` when the id was saved; `false` for an id with anything other than letters, digits and `_ @ . : + -`, or longer than 128 characters (nothing is saved then). The id is kept on the phone until you change it, and this install reports it to Appwright Push a moment later (or on the next launch when it is offline). **Needs:** turn on **Appwright Push** in the Integrate step when you build. Sending to a user reaches phones running an app built with Appwright from 30 Sep 2026 on; older builds still get messages sent to Everyone.

**After sign-in and on sign-out** - use your database id, never an email address or phone number:

```js
function pushUser(id) {
  if (!(window.WebToApk && window.WebToApk.setPushUser)) return false;   // browser, or an older build
  return WebToApk.setPushUser(id);
}

async function signIn(email, password) {
  const user = await myBackend.signIn(email, password);   // your own sign-in
  pushUser(user.id);                                      // e.g. 'u_42' or a UUID
}

async function signOut() {
  await myBackend.signOut();
  pushUser('');                                           // this phone is nobody's now
}
```

**Supabase:** follow the session, so a restored session counts as well as a fresh sign-in:

```js
supabase.auth.onAuthStateChange(function (event, session) {
  if (!(window.WebToApk && window.WebToApk.setPushUser)) return;
  const id = session ? session.user.id : '';     // the auth UUID: letters, digits and '-'
  if (WebToApk.getPushUser() !== id) WebToApk.setPushUser(id);
});
```

**Make an id acceptable** when yours may contain other characters (spaces, `/`, `#`), and log the rare one that is still refused:

```js
function pushUserIdFrom(rawId) {
  return String(rawId).trim().replace(/[^A-Za-z0-9_@.:+\-]/g, '_').slice(0, 128);
}

if (window.WebToApk && window.WebToApk.setPushUser) {
  const ok = WebToApk.setPushUser(pushUserIdFrom(currentUser.id));
  if (!ok) console.warn('push user id refused', currentUser.id);
}
```

**Notes:** the same user signed in on two phones is reached on both. The id belongs to this install, not to the account - sign-out must clear it, or the phone keeps receiving the previous user's messages. `setPushUser('')` does not clear tags set with `setPushTag`; clear user-specific tags on sign-out too. Android only: the iOS app has no `WebToApk.setPushUser`, so the feature check skips it there.

