# Phone, SMS and the call log - JavaScript bridge API

> Placing a call, sending or drafting a message, and reading history. Every one of these is permission-gated and Play asks about them.

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

### `composeSms`

```js
window.WebToApk.composeSms(phoneNumber: String, message: String): Boolean
```

Opens the user's SMS app with the message prefilled. Needs no permission and cannot be rejected by Play - the right default for anything non-automated. Returns false when the phone has no SMS app (or it could not be opened).

**Example**

Opens the user's SMS app with the number and the text already filled in. The user presses send.

**Returns:** `true` when the SMS app opened, `false` when the phone has no SMS app or it could not be opened (synchronous). **Needs:** nothing - no build switch, no permission.

**A "Text us" button.**

```js
document.getElementById('textUs').addEventListener('click', function () {
  if (window.WebToApk && window.WebToApk.composeSms) {
    var ok = WebToApk.composeSms('+911234567890', 'Hello, I need help with my order');
    if (!ok) showMessage('Could not open the SMS app.');
  } else {
    // In a normal browser the standard sms: link does the same job.
    location.href = 'sms:+911234567890?body=' + encodeURIComponent('Hello, I need help with my order');
  }
});
```

**Notes:** Nothing is sent until the user taps send in their own SMS app, so this method itself needs no SMS permission. Use this for every message the user writes or approves; use `sendSms` only when the app really must send by itself. For AI-built apps and Google AI Studio imports the build scans your code; a `composeSms(` call does not tick **SMS (read & send)** - only `sendSms` and `listSms` do - so a "Text us" button adds no Play-restricted permission.

### `listCallLog`

```js
window.WebToApk.listCallLog(requestId: String, limit: Int, offset: Int, sinceMillis: String)
```

WebToApk.listCallLog(id, limit, offset, sinceMillis) Delivered as `appmint:calllog` / window.onAppMintCallLog.

_Described by its group, Call log, rather than on its own._

**Example**

Reads the phone's call history, newest call first.

**Returns:** nothing now. The answer is the `appmint:calllog` event (and `window.onAppMintCallLog`) with `detail = { requestId, calls: [{ number, name, type, date, duration }] }` or `{ requestId, error }`. `date` is milliseconds since 1970; `duration` is seconds. **Needs:** turn on **Call log (read)** in Step 4 (Access) when you build; the user is asked for permission on the first call.

**Show the last 50 calls.**

```js
function loadCalls() {
  if (!(window.WebToApk && window.WebToApk.listCallLog)) return;
  var id = 'calls-' + Date.now();

  window.addEventListener('appmint:calllog', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:calllog', handler);
    if (e.detail.error) { showMessage('Call log: ' + e.detail.error); return; }

    e.detail.calls.forEach(function (c) {
      // c.type: incoming | outgoing | missed | voicemail | rejected | blocked | other
      addRow((c.name || c.number) + ' - ' + c.type + ' - ' +
             new Date(c.date).toLocaleString() + ' - ' + c.duration + 's');
    });
  });

  WebToApk.listCallLog(id, 50, 0, '0');   // limit, offset, since (string)
}
```

**Only calls from the last 24 hours**, using the page hook. `sinceMillis` is a STRING because the number is too big for the bridge's int.

```js
window.onAppMintCallLog = function (detail) {
  if (detail.requestId !== 'today') return;
  if (detail.error) return;
  document.getElementById('count').textContent = detail.calls.length + ' calls today';
};

if (window.WebToApk && window.WebToApk.listCallLog) {
  WebToApk.listCallLog('today', 500, 0, String(Date.now() - 24 * 60 * 60 * 1000));
}
```

**Notes:** `limit` is clamped to 1-2000. Errors: `not_enabled`, `permission_denied`, `foreign_origin`. Call log is a Google Play restricted permission: you must file an approved Permissions Declaration in Play Console, or the app is rejected.

### `listSms`

```js
window.WebToApk.listSms(requestId: String, box: String, limit: Int, offset: Int)
```

WebToApk.composeSms(number, message)   <- NO permission, opens the SMS app   WebToApk.sendSms(id, number, message, wantDelivery)   WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) Send results: `appmint:sms` / window.onAppMintSms   {requestId, status:"sent"|"delivered"|"failed", ok, reason?} Inbound messages: `appmint:sms-received` / window.onAppMintSmsReceived.

_Described by its group, SMS, rather than on its own._

**Example**

Reads SMS messages from the phone's inbox, sent box or drafts, newest first. This file also shows how to receive new messages as they arrive.

**Returns:** nothing now. The answer is the `appmint:sms` event (and `window.onAppMintSms`) with `detail = { requestId, messages: [{ id, address, body, date, read, box }] }`. On failure: `{ requestId, status: 'failed', ok: false, reason }`. **Needs:** turn on **SMS (read & send)** in Step 4 (Access) when you build; the user is asked for permission on the first call.

**Read the latest 30 inbox messages.**

```js
function loadInbox() {
  if (!(window.WebToApk && window.WebToApk.listSms)) return;
  var id = 'inbox-' + Date.now();

  window.addEventListener('appmint:sms', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:sms', handler);
    if (e.detail.status === 'failed') { showMessage('Cannot read SMS: ' + e.detail.reason); return; }

    e.detail.messages.forEach(function (m) {
      addRow(m.address + ': ' + m.body + (m.read ? '' : ' (new)') +
             ' - ' + new Date(m.date).toLocaleString());
    });
  });

  WebToApk.listSms(id, 'inbox', 30, 0);   // box: 'inbox' | 'sent' | 'draft'
}
```

**New messages as they arrive** (`appmint:sms-received`, also `window.onAppMintSmsReceived`). Messages that arrive while the app is closed are kept (the last 50) and given to your page when the app opens again.

```js
window.addEventListener('appmint:sms-received', function (e) {
  // e.detail = { address: '+91...', body: 'Your OTP is 4821', date: 1737000000000 }
  var code = (e.detail.body.match(/\b\d{4,6}\b/) || [])[0];
  if (code) document.getElementById('otp').value = code;
});

// or, as a page hook:
window.onAppMintSmsReceived = function (sms) {
  console.log('SMS from ' + sms.address);
};
```

**Notes:** `limit` is clamped to 1-500. Any unknown box name reads the inbox. `reason` values: `not_enabled`, `permission_denied`, `foreign_origin`. Received messages still reach the user's normal SMS app too. SMS is a Google Play restricted permission: you must file an approved Permissions Declaration in Play Console (usually only default SMS apps qualify), or the app is rejected.

### `makePhoneCall`

```js
window.WebToApk.makePhoneCall(phoneNumber: String)
```

Calls [phoneNumber]. - Phone permission ON in the build: places the call directly. The first time, Android asks the user for the Phone permission; if they refuse, the dialer opens with the number filled in so they can still place it themselves. - Phone permission OFF: opens the dialer with the number filled in (the dialer needs no permission) and the user taps Call. - A page that is not the app's own (an external site open in the app) always gets the dialer, never a direct call. A plain `tel:` link in your HTML also opens the dialer and is often enough.

**Example**

Starts a phone call to a number, or opens the dialer with the number filled in.

**Returns:** nothing. **Needs:** nothing to open the dialer. Turn on **Phone** in Step 4 (Access) when you build if the call should start directly.

**A "Call the shop" button.**

```js
document.getElementById('callShop').addEventListener('click', function () {
  if (window.WebToApk && window.WebToApk.makePhoneCall) {
    WebToApk.makePhoneCall('+911234567890');
  }
});
```

**No switch needed: a plain `tel:` link** opens the dialer with the number, in the app and in a browser. This is often all you need.

```html
<a href="tel:+911234567890">Call the shop</a>
```

**Notes:** With **Phone** off, the dialer opens with the number and the user presses call. With **Phone** on, the call starts directly; the first time, Android asks the user for the phone-call permission, and if they refuse, the dialer opens instead so they can still place the call. A page that is not the app's own (an external site open inside the app) always gets the dialer. Pass the number as a plain string. Nothing is reported back - you cannot know if the call was placed. The Phone switch adds the CALL_PHONE permission to your Play listing; declare it in the Data safety form and only turn it on if you need it.

### `sendSms`

```js
window.WebToApk.sendSms(
```

WebToApk.composeSms(number, message)   <- NO permission, opens the SMS app   WebToApk.sendSms(id, number, message, wantDelivery)   WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) Send results: `appmint:sms` / window.onAppMintSms   {requestId, status:"sent"|"delivered"|"failed", ok, reason?} Inbound messages: `appmint:sms-received` / window.onAppMintSmsReceived.

_Described by its group, SMS, rather than on its own._

**Example**

Sends an SMS directly from the app, without opening the SMS app, and reports what really happened.

**Returns:** nothing now. The answer is the `appmint:sms` event (and `window.onAppMintSms`) with `detail = { requestId, status: 'sent' | 'delivered' | 'failed', ok, reason? }`. With a delivery report you get TWO answers on success: `sent`, then `delivered`. **Needs:** turn on **SMS (read & send)** in Step 4 (Access) when you build; the user is asked for permission on the first call.

**Send a code and follow its status.** Do not remove the listener after the first answer when you asked for a delivery report.

```js
function sendCode(phone, code) {
  if (!(window.WebToApk && window.WebToApk.sendSms)) {
    showMessage('Sending SMS works only inside the app.');
    return;
  }
  var id = 'sms-' + Date.now();

  function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    var d = e.detail;
    if (d.status === 'sent' && d.reason === 'no_delivery_report') {
      showStatus('Sent (the network gave no delivery report)');
      window.removeEventListener('appmint:sms', handler);
    } else if (d.status === 'sent') {
      showStatus('Sent');
    } else if (d.status === 'delivered') {
      showStatus('Delivered');
      window.removeEventListener('appmint:sms', handler);
    } else {                                   // 'failed'
      showStatus('Failed: ' + d.reason);
      window.removeEventListener('appmint:sms', handler);
    }
  }
  window.addEventListener('appmint:sms', handler);

  WebToApk.sendSms(id, phone, 'Your code is ' + code, true);   // true = delivery report
}
```

**No delivery report.** Pass `false` as the last value: you get exactly one answer, `sent` or `failed`.

```js
window.onAppMintSms = function (d) {
  if (d.requestId === 'alert-1') showStatus(d.ok ? 'Alert sent' : 'Failed: ' + d.reason);
};
if (window.WebToApk && window.WebToApk.sendSms) {
  WebToApk.sendSms('alert-1', '+911234567890', 'Gate opened at ' + new Date().toLocaleTimeString(), false);
}
```

**Notes:** Long text is split into parts and sent as one message. If no delivery report arrives within 60 seconds you get `status: 'sent'` with `reason: 'no_delivery_report'` - the message was still sent. `reason` values: `not_enabled`, `permission_denied`, `foreign_origin`, `invalid_arguments` (empty number or text), `no_service`, `radio_off`, `generic_failure`, `null_pdu`, `not_delivered`, and others. Use a string `requestId`. Each SMS may cost the user money. SMS is a Google Play restricted permission: you must file an approved Permissions Declaration in Play Console (usually only default SMS apps qualify), so prefer `composeSms` when the user can press send.

