# Node.js SDK

The official JavaScript SDK is published on npm as **`node-mailjet`** and developed at [mailjet/mailjet-apiv3-nodejs](https://github.com/mailjet/mailjet-apiv3-nodejs). It is written in TypeScript and ships full type coverage.

The SDK runs in **Node.js** and in the **browser**. In the browser a proxy is currently required because of CORS restrictions — and your private API key must never be shipped in frontend code.

## Compatibility

Officially supported Node.js versions: **>= 12.x**.

## Upgrading to v7

Version **7.0.0** contains breaking changes. In short:

- **Entry points are restricted.** `package.json` now declares an `exports` map, so only `require('node-mailjet')` and `import ... from 'node-mailjet'` resolve. Deep imports such as `require('node-mailjet/dist/mailjet.node.js')` no longer work.
- **`.id()` must come before `.action()`.** Calling `.id()` after `.action()` now throws instead of silently building an incorrect URL.
- **`contactdata` PUT is validated locally.** A `PUT` to `contactdata` throws synchronously if `Data` is missing, is not an array, or contains entries without `Name` / `Value` keys — instead of failing later with a server-side `400`.
- **Requests time out after 30s by default** instead of hanging indefinitely. Pass `options.timeout` to change it.


The full list is in the [CHANGELOG](https://github.com/mailjet/mailjet-apiv3-nodejs/blob/master/CHANGELOG.md).

## Installation

```bash
npm install node-mailjet
```

## Set up the client

### Authentication

The Email API and Send API authenticate with your API key and secret. The SMS API uses a bearer token generated in the [SMS section](https://app.mailjet.com/sms) of your account.

```bash
export MJ_APIKEY_PUBLIC='your API key'
export MJ_APIKEY_PRIVATE='your API secret'
export MJ_API_TOKEN='your API token'
```

CommonJS and native ESM (including named imports) are both supported:

```javascript
const Mailjet = require('node-mailjet');
```

```javascript
import Mailjet, { Client } from 'node-mailjet';
```

### Email API and Send API

```javascript
const mailjet = new Mailjet({
  apiKey: process.env.MJ_APIKEY_PUBLIC,
  apiSecret: process.env.MJ_APIKEY_PRIVATE
});
```

Or with the `apiConnect` helper:

```javascript
const mailjet = Mailjet.apiConnect(
  process.env.MJ_APIKEY_PUBLIC,
  process.env.MJ_APIKEY_PRIVATE,
  {
    config: {},
    options: {}
  }
);
```

### SMS API

```javascript
const mailjet = Mailjet.smsConnect(process.env.MJ_API_TOKEN);
```

## Send your first email

```javascript
const Mailjet = require('node-mailjet');

const mailjet = Mailjet.apiConnect(
  process.env.MJ_APIKEY_PUBLIC,
  process.env.MJ_APIKEY_PRIVATE,
);

mailjet
  .post('send', { version: 'v3.1' })
  .request({
    Messages: [
      {
        From: {
          Email: 'pilot@example.com',
          Name: 'Mailjet Pilot'
        },
        To: [
          {
            Email: 'passenger1@example.com',
            Name: 'Passenger 1'
          }
        ],
        Subject: 'Your email flight plan!',
        TextPart: 'Dear passenger 1, welcome to Mailjet!',
        HTMLPart: '<h3>Dear passenger 1, welcome to Mailjet!</h3>'
      }
    ]
  })
  .then((result) => console.log(result.body))
  .catch((err) => console.log(err.statusCode));
```

## Configuration

Requests are composed from a method, a resource, a config object and the request data:

```javascript
const mailjet = new Mailjet({
  apiKey: process.env.MJ_APIKEY_PUBLIC,
  apiSecret: process.env.MJ_APIKEY_PRIVATE,
  config: CONFIG,
  options: OPTIONS
});

const request = mailjet
  .METHOD(RESOURCE, CONFIG)
  .request(DATA, PARAMS, PERFORM_API_CALL);
```

| Argument | Meaning |
|  --- | --- |
| `METHOD` | `post`, `put`, `get` or `delete` |
| `RESOURCE` | the API endpoint to call |
| `CONFIG` | connection config — see [Config](#config) |
| `OPTIONS` | connection options — see [Options](#options) |
| `DATA` | request body (`post`, `put`, `delete`) |
| `PARAMS` | URL query parameters |
| `PERFORM_API_CALL` | boolean; set `false` to build the request without sending it |


Config and options can be set once on the client and reused for every request, or passed per request — a per-request config takes precedence.

### Options

| Option | Effect |
|  --- | --- |
| `headers` | additional request headers |
| `timeout` | milliseconds before the request is aborted (default `30000` since v7) |
| `proxy` | proxy host, port, protocol and auth *(Node only)* |
| `maxBodyLength` | maximum request content size in bytes *(Node only)* |
| `maxContentLength` | maximum response content size in bytes *(Node only)* |


```javascript
const mailjet = new Mailjet({
  apiKey: process.env.MJ_APIKEY_PUBLIC,
  apiSecret: process.env.MJ_APIKEY_PRIVATE,
  options: {
    timeout: 1000,
    maxBodyLength: 1500,
    maxContentLength: 100,
    headers: {
      'X-API-Key': 'foobar',
    },
    proxy: {
      protocol: 'http',
      host: 'www.test-proxy.com',
      port: 3100,
    }
  }
});
```

Options are passed through to the underlying HTTP client — see the [axios request config](https://github.com/axios/axios#request-config) for details.

### Config

| Key | Effect |
|  --- | --- |
| `host` | base host name (default `api.mailjet.com`) |
| `version` | API version — `v3`, `v3.1` or `v4` |
| `output` | response data type |


#### API versioning

| Version | Scope |
|  --- | --- |
| `v3` | Email API |
| `v3.1` | Send API v3.1 (latest send version) |
| `v4` | SMS API, contact delete API |


Most Email API endpoints sit under `v3`, which is the default and does not need to be specified. For anything else, set `version`:

```javascript
const request = mailjet.post('send', { version: 'v3.1' });
```

#### Host URL

```javascript
const request = mailjet.post('send', { version: 'v3.1', host: 'api.us.mailjet.com' });
```

Accounts on Mailjet's **US architecture** must set `api.us.mailjet.com`.

#### Response output

The default output is `json`. Supported values: `arraybuffer`, `document`, `json`, `text`, `stream`, and `blob` (browser only).

```javascript
const request = mailjet.post('send', { version: 'v3.1', output: 'arraybuffer' });
```

### Disable the API call

Useful in tests, to build and inspect a request without reaching Mailjet:

```javascript
const request = mailjet
  .post('send', { version: 'v3.1' })
  .request({}, {}, false);
```

## TypeScript

All Mailjet types are exported from the package entry point:

```typescript
import {
  Contact,
  SendEmailV3,
  SendEmailV3_1,
  Message,
  Segmentation,
  Template,
  SendMessage,
  Webhook
} from 'node-mailjet';
```

`Request.request<TResult>(data, params, performAPICall)` is generic, so responses are typed:

```typescript
import { Client, SendEmailV3_1, LibraryResponse } from 'node-mailjet';

const mailjet = new Client({
  apiKey: process.env.MJ_APIKEY_PUBLIC,
  apiSecret: process.env.MJ_APIKEY_PRIVATE
});

const data: SendEmailV3_1.Body = {
  Messages: [
    {
      From: { Email: 'pilot@example.com' },
      To: [{ Email: 'passenger@example.com' }],
      Subject: 'Your email flight plan!',
      HTMLPart: '<h3>Dear passenger, welcome to Mailjet!</h3>',
      TextPart: 'Dear passenger, welcome to Mailjet!',
    },
  ],
};

const result: LibraryResponse<SendEmailV3_1.Response> = await mailjet
  .post('send', { version: 'v3.1' })
  .request(data);

const { Status } = result.body.Messages[0];
```

For library versions `3.*.*` and below, use the community-maintained [`@types/node-mailjet`](https://www.npmjs.com/package/@types/node-mailjet) package instead.

## Batch sending and partial failures

Send API v3.1 accepts multiple emails in one request through the `Messages` array. **This is not an atomic operation.** Each entry is validated and processed independently, so a single request can return a mix of successes and failures. A `2xx` response only means Mailjet accepted the request for processing — it does not mean every message was sent, and one failing message does not stop the others.

Always inspect the `Status` of each entry in `result.body.Messages`:

- `success` — the message was accepted and is being delivered.
- `error` — the message failed; details are in that entry's `Errors` array (`ErrorCode`, `ErrorMessage`, `ErrorIdentifier`).


Set `AdvanceErrorHandling: true` to get one `Errors` entry per validation problem, with machine-readable codes.

```typescript
const data: SendEmailV3_1.Body = {
  Messages: [
    {
      From: { Email: 'pilot@example.com' },
      To: [{ Email: 'passenger1@example.com' }],
      Subject: 'Your flight plan (1)',
      TextPart: 'This message is expected to succeed.',
      CustomID: 'order-1001',
    },
    {
      From: { Email: 'pilot@example.com' },
      To: [{ Email: 'not-a-valid-address' }],
      Subject: 'Your flight plan (2)',
      TextPart: 'This message is expected to fail.',
      CustomID: 'order-1002',
    },
  ],
  AdvanceErrorHandling: true,
};

const result: LibraryResponse<SendEmailV3_1.Response> = await mailjet
  .post('send', { version: 'v3.1' })
  .request(data);

const succeeded = result.body.Messages.filter((message) => message.Status === 'success');
const failed = result.body.Messages.filter((message) => message.Status !== 'success');
```

Note that `failed` being non-empty does **not** mean the promise rejected — the request itself succeeded.

### Retrying without duplicating sends

Blindly retrying a whole batch resends messages that already succeeded. To retry safely:

1. Give every message a unique `CustomID` before you send it, and record it against your own order or notification ID.
2. Collect only the messages whose `Status` is `error` — never re-include successes.
3. Use the failed entry's `Errors[].StatusCode` / `ErrorCode` to decide whether a retry makes sense. Validation errors (invalid recipient, malformed address, blocked domain) will fail again unless the message itself changes; transient errors (`StatusCode >= 500`) may succeed.
4. Resubmit only the failed messages, reusing the same `CustomID`, so retries stay correlated in your logs and in Mailjet's.
5. Treat a request-level rejection (network failure, auth failure, malformed payload) differently: Mailjet never processed the batch, so the entire `Messages` array is safe to retry as-is.


A runnable example lives in [`examples/node/src/batchSend.js`](https://github.com/mailjet/mailjet-apiv3-nodejs/tree/master/examples/node/src/batchSend.js).

## Request examples

### POST — create an object

```javascript
mailjet
  .post('contact')
  .request({
    Email: 'passenger@example.com',
    IsExcludedFromCampaigns: true,
    Name: 'New Contact'
  })
  .then((result) => console.log(result.body))
  .catch((err) => console.log(err.statusCode));
```

### POST — endpoints with an action

Call `.id()` **before** `.action()`:

```javascript
mailjet
  .post('contact')
  .id(contactID)
  .action('managecontactslists')
  .request({
    ContactsLists: [
      {
        ListID: listID,
        Action: 'addnoforce'
      }
    ]
  })
  .then((result) => console.log(result.body))
  .catch((err) => console.log(err.statusCode));
```

### GET — all objects

```javascript
mailjet.get('contact').request();
```

### GET — with filters

Query parameters go in the second `.request()` argument:

```javascript
mailjet.get('contact').request({}, { IsExcludedFromCampaigns: false });
```

### GET — a single object

```javascript
mailjet.get('contact').id(contactID).request();
```

For the `contact` and `contactdata` resources, `.id()` also accepts a contact's **email address** instead of the numeric ID (`.id('user@example.com')`). Pass it as-is — the SDK does not URL-encode it, which matches what the REST API expects.

### PUT — update an object

A `PUT` in the Mailjet API behaves like a `PATCH`: only the properties you send are updated, and non-mandatory properties can be omitted.

Each entry in `Data` must be an object with `Name` and `Value` keys, where `Name` matches a property already defined via [`/contactmetadata`](https://dev.mailjet.com/email/reference/contacts/contact-properties/#v3_post_contactmetadata). Sending the property directly (`{ first_name: 'John' }`) instead of `{ Name: 'first_name', Value: 'John' }` returns an `Invalid key name` error.

```javascript
mailjet
  .put('contactdata')
  .id(contactID)
  .request({
    Data: [
      { Name: 'first_name', Value: 'John' },
      { Name: 'last_name', Value: 'Smith' }
    ]
  });
```

#### Updating template content

`PUT` / `POST` on the `template` resource only updates metadata (`Name`, `Categories`, `Purposes`, …). It does **not** touch the template body, so changes made that way appear in a `GET template` response but never in the Mailjet App editor. To update the actual content, target the `template/{id}/detailcontent` sub-resource:

```javascript
mailjet
  .put('template')
  .id(templateID)
  .action('detailcontent')
  .request({
    'Html-part': '<html><body><p>Hello {{var:name}}</p></body></html>',
    'Text-part': 'Hello {{var:name}}'
  });
```

### DELETE — remove an object

A successful `DELETE` returns `204 No Content` with no response body.

```javascript
mailjet.delete('template').id(templateID).request();
```

A `204` here — and a subsequent `404` on `GET template/{id}` — confirms the template was removed API-side.

#### Deleting a contact (GDPR)

Contact deletion uses a dedicated `v4` endpoint, so the resource name is the **plural** `contacts` and the version must be set explicitly:

```typescript
await mailjet
  .delete('contacts', { version: 'v4' })
  .id(contactID)
  .request();
```

See [Delete a contact](https://dev.mailjet.com/email/reference/contacts/contact/#v4_delete_contact_contact_ID) for details.

## SMS API

```javascript
const Mailjet = require('node-mailjet');

const mailjet = Mailjet.smsConnect(process.env.MJ_API_TOKEN, {
  config: { version: 'v4' }
});

mailjet
  .post('sms-send')
  .request({
    Text: 'Have a nice SMS flight with Mailjet!',
    To: '+33600000000',
    From: 'MJPilot'
  })
  .then((result) => console.log(result.body))
  .catch((err) => console.log(err.statusCode));
```

## Example applications

The repository includes runnable examples for several environments:

- [Browser](https://github.com/mailjet/mailjet-apiv3-nodejs/tree/master/examples/browser) — RequireJS-based request playground
- [Node](https://github.com/mailjet/mailjet-apiv3-nodejs/tree/master/examples/node) — simple request scripts
- [Sendmail](https://github.com/mailjet/mailjet-apiv3-nodejs/tree/master/examples/sendmail) — Express app that lists contacts and sends email
- [React](https://github.com/mailjet/mailjet-apiv3-nodejs/tree/master/examples/react) — React request playground
- [Firebase](https://github.com/mailjet/mailjet-apiv3-nodejs/tree/master/examples/firebase) — Firebase Functions for static and query-driven sends


Browser-side examples still require a proxy because of CORS.

## Contribute

The SDK is open source. Fork the repository, branch, implement your fix or feature, document it, and open a pull request at [mailjet/mailjet-apiv3-nodejs](https://github.com/mailjet/mailjet-apiv3-nodejs). Documentation improvements belong in the [API documentation repo](https://github.com/mailjet/api-documentation).