Two entries
| Entry | Runs in | Key | What it does |
|---|---|---|---|
| @maqetin/sdk-js | Browser, Node | Publishable key, mq_live_… | identify, track (data in) |
| @maqetin/sdk-js/server | Node only | Secret key, mq_sec_… | messages.send (transactional messages out) |
Both entries have the same version. A web bundle that imports the main entry never holds the server entry.
npm install @maqetin/sdk-jsBoth entries are ES modules. A CommonJS project loads them with require() on Node 22.12 or later.
Send events from the browser
import { MaqetinClient } from '@maqetin/sdk-js';
const mq = new MaqetinClient({ publicKey: 'mq_live_…' });
mq.identify('u_42', { email: 'ada@example.com', firstName: 'Ada' });
mq.track('checkout.completed', { total: 12.5 });- The client refuses a secret key.
- Calls go in batches to https://in.maqetin.com/v1/batch, and are sent again after a 5xx, a 429 or a network error. Set endpoint to use another host.
- A request that does not answer is given up after requestTimeoutMs, 10 seconds by default, and sent again with the same message ids, so an event is stored once.
- A call is refused alone, and reported to onError, when it holds a value JSON cannot hold, when it is over 32 KiB as JSON, when a typed trait has the wrong type, or when timestamp is not a valid Date. The rest of its batch is sent.
- A call is fixed when you make it. Changing the properties object after track does not change the event.
- Queued calls are sent when the page closes or goes to the background.
Call reset() when someone logs out. Without the call, the next person who uses the same browser is tracked as the previous one: their events go on the previous profile, and can start journeys that email the previous person.
async function logout() {
await signOut();
mq.reset();
}Page views
The client sends a page view, page.viewed, when it starts and when a single-page app moves to a new path. A change of the query string or the fragment alone is not a new page.
Set autoPageViews: false to send page views yourself.
mq.page(); // the current page
mq.page({ section: 'pricing' }); // with your own propertiesThe page view holds path, url, title and referrer in its properties, so a journey can start on a path. Your own properties never replace those four.
The first view is sent when the client starts, so its title can be empty if you load the SDK in <head>. The referrer is the page the visitor came from when the site loaded.
What the SDK collects
Tell the people who use your site about these fields in your privacy notice. Set disableAutoCollection: true to leave them out of context. Two still go out: anonymousId, which every call carries, and the page, which page views carry in their properties until you set autoPageViews: false.
| Field | What it holds |
|---|---|
| timezone, locale | The IANA time zone and the language of the browser. |
| deviceId, anonymousId | Random ids the SDK makes and keeps in the browser's storage. Never an advertising id. |
| userAgent | The string the browser sends to name itself. |
| page.url, page.path, page.referrer | The address of the page and of the page before it, without the query string or the fragment. |
| page.title | The page title as your page sets it. If your titles hold a name, so does this field. |
| campaign | The utm_source, utm_medium, utm_campaign, utm_term and utm_content of the address, only when it has them. |
What the SDK reads and writes on the device
Every read and write the client makes on the visitor's device, with the version that started it. None of them asks the browser for a permission. The server entry runs in Node and touches no visitor's device. Each release's README holds the table for that version.
| Since | What | Read or write | When | Why | How to stop it |
|---|---|---|---|---|---|
| 0.1.0 | localStorage key maqetin.anonymousId | Read and write | When the client starts; written again by reset() | Links a visitor's events before and after they sign in. | storage: memoryStorage(): the id lasts until the page closes. |
| 0.1.0 | localStorage key maqetin.userId | Read, write and remove | Read when the client starts, written by identify, removed by reset() | Keeps the visitor identified after a page load. | storage: memoryStorage(): the visitor is identified on that page only. |
| 0.1.0 | localStorage key maqetin.deviceId | Read and write | Read when the client starts, to learn whether the page may use storage; then read with each call, and written the first time | context.deviceId: tells one browser of a person from another. | disableAutoCollection: true stops it with each call; storage: memoryStorage() stops the read at start as well. |
| 0.1.0 | Time zone (Intl.DateTimeFormat().resolvedOptions().timeZone) | Read | With each call | context.timezone, kept on the person's profile. | disableAutoCollection: true |
| 0.1.0 | Language (navigator.language) | Read | With each call | context.locale, kept on the person's profile. | disableAutoCollection: true |
| 0.1.0 | The string the browser sends to name itself (navigator.userAgent) | Read | With each call | context.userAgent | disableAutoCollection: true |
| 0.1.0 | Page address (location.href) | Read | With each call, and with each page view | context.page, context.campaign and the page view's properties. The query string and the fragment are dropped; only the utm_* values are taken from the query. | disableAutoCollection: true, autoPageViews: false, and no page() call. |
| 0.1.0 | Page address (location.href) | Read | When the client starts, only if endpoint is a path | Makes the full address of your endpoint; nothing is sent. | Give endpoint as a full https:// address. |
| 0.1.0 | Referring page (document.referrer) | Read | With each call, and with each page view | context.page.referrer and the page view's referrer, without the query string or the fragment. | disableAutoCollection: true, autoPageViews: false, and no page() call. |
| 0.1.0 | Page title (document.title) | Read | With each call, and with each page view | context.page.title and the page view's title. | disableAutoCollection: true, autoPageViews: false, and no page() call. |
If the browser refuses storage, the three ids last until the page closes. The client also watches for the page closing or going to the background, and for a single-page app moving to a new path, so it can send queued calls and record page views. It reads only whether the page is hidden. The SDK sets no cookie.
Send email from your server
import { MaqetinServer } from '@maqetin/sdk-js/server';
const mq = new MaqetinServer({ secretKey: process.env.MAQETIN_SECRET_KEY });
const { messageId } = await mq.messages.send({
channel: 'email',
templateId: 'tpl_receipt',
to: { userId: 'u_42' }, // or { email: 'guest@example.com' }
from: 'Harbour Money <receipts@mail.harbourmoney.co.uk>',
data: { order: { id: 'o_42', total: 12.5 } },
idempotencyKey: 'order-42:receipt',
});- The secret key needs the messages:send scope. Sends are limited to 300 a minute per key.
- The client refuses a public key and refuses to run in a web page.
- Messages go to https://api.maqetin.com/v1/public/messages. Set endpoint to use another host.
- A 4xx is thrown at once as MaqetinRequestError with status, code and message.
The send API page lists the fields and the answers.
Send through your own domain
If a blocker list names in.maqetin.com, a visitor who uses that blocker sends nothing. To avoid this on a web page, send through a path on your own site and let your server forward it to Maqetin.
- Choose a path your site does not use. This page uses /relay.
- Forward the batch route. Forward POST /relay/v1/batch to https://in.maqetin.com/v1/batch.
- Give the path as endpoint. The SDK adds /v1/batch to it.
const mq = new MaqetinClient({ publicKey: 'mq_live_…', endpoint: '/relay' });Your proxy must forward POST <path>/v1/batch as POST /v1/batch on in.maqetin.com, with the headers Authorization and Content-Type and the body unchanged. It sends Host: in.maqetin.com, returns the status, the Retry-After header and the body of the answer unchanged, and accepts a body of 512 KiB or more.
Your proxy must not cache the answer, and must not retry on its own: the SDK already retries with backoff and with the same message ids. Do not log the body, which holds the data of your visitors.