# ZeptoMail Node.js SDK

Send transactional email and manage your ZeptoMail account — agents, domains, suppression lists, and email logs from Node.js.

## Table of Contents

- [Installation](#installation)
- [Requirements](#requirements)
- [Email Sending](#email-sending)
  - [Send mail](#send-mail)
  - [Send batch mail](#send-batch-mail)
  - [Send template mail](#send-template-mail)
  - [Send batch template mail](#send-batch-template-mail)
- [Authentication](#authentication--one-client-per-scope)
  - [Rotating a refresh token](#rotating-a-refresh-token)
  - [Pre-warming the token at startup](#pre-warming-the-token-at-startup)
  - [Disabling auto-refresh](#disabling-auto-refresh)
- [Agent Management](#-agent-management)
- [Domain Management](#-domain-management)
- [Suppression Management](#-suppression-management)
- [Email Log Retrieval](#-email-log-retrieval)
- [Error Handling](#-error-handling)
- [Contact Us](#-contact-us)

---

## Installation

Sign up at [ZeptoMail](https://www.zoho.com/zeptomail/) if you haven't already, then install the package:

```bash
npm install zeptomail
```

---

## Requirements

- **Node.js ≥ 20**
- Full TypeScript support is included — no separate `@types/` package needed.

---

> **Note:** Email sending uses a **Send Mail Token** for authentication and the management clients (Agents, Domains, Suppression, Email Logs) use **OAuth 2.0** via AuthenticationClient.

## Email Sending

Obtain your Send Mail Token from the [ZeptoMail dashboard](https://www.zoho.com/zeptomail/help/smtp-api.html#alink2).

```js
import { SendMailClient } from "zeptomail";
// CommonJS: const { SendMailClient } = require("zeptomail");

const client = new SendMailClient({
    url: "api.zeptomail.com/", // required
    token: "<Send mail token>", // required
});
```

### Send mail

```js
await client.sendMail({
    from: { address: "sender@example.com", name: "Sender" },
    to: [
        { email_address: { address: "to@example.com", name: "Recipient" } }
    ],
    reply_to: [{ address: "reply@example.com", name: "Reply" }],
    cc: [{ email_address: { address: "cc@example.com", name: "CC" } }],
    bcc: [{ email_address: { address: "bcc@example.com", name: "BCC" } }],
    subject: "Sending with ZeptoMail",
    htmlbody: "<strong>Easy to do from anywhere, with Node.js</strong>",
    textbody: "Easy to do from anywhere, with Node.js",
    track_clicks: true,
    track_opens: true,
    client_reference: "<client reference>",
    mime_headers: { "X-Zylker-User": "test-xxxx" },
    attachments: [
        { content: "<base64>", mime_type: "image/jpg", name: "report.jpg" },
        { file_cache_key: "<File Cache Key>", name: "cached-file" }
    ],
    inline_images: [
        { content: "<base64>", mime_type: "image/jpg", cid: "img-header" },
        { file_cache_key: "<File Cache Key>", cid: "img-footer" }
    ]
});
```

### Send batch mail

Batch mail lets you send one request for up to **500 recipients**, with per-recipient personalisation via `merge_info`. Use `{{placeholder}}` syntax in `subject`, `htmlbody`, `textbody`, or `client_reference`. The matching keys from each recipient's `merge_info` are substituted before delivery.

**Two `merge_info` placement modes:**

- **Per-recipient only** — set `merge_info` inside each `to` entry. Each recipient gets their own values.
- **Per-recipient + global fallback** — set `merge_info` both inside `to` entries and at the top level. Recipients with their own `merge_info` use it; recipients without one fall back to the top-level values.

```js
await client.sendBatchMail({
    from: { address: "sender@example.com", name: "Sender" },
    to: [
        {
            email_address: { address: "alice@example.com", name: "Alice" },
            merge_info: { name: "Alice", company: "Acme" }  // per-recipient values
        },
        {
            email_address: { address: "bob@example.com", name: "Bob" },
            merge_info: { name: "Bob", company: "Globex" }
        },
        {
            email_address: { address: "carol@example.com", name: "Carol" }
            // no merge_info — falls back to top-level merge_info below
        }
    ],
    // Top-level merge_info: used as fallback for recipients without their own
    merge_info: { name: "Customer", company: "Your Company" },
    reply_to: [{ address: "reply@example.com", name: "Reply" }],
    subject: "Hi {{name}}, a message from {{company}}",
    htmlbody: "<p>Hi {{name}}, welcome to {{company}}.</p>",
    textbody: "Hi {{name}}, welcome to {{company}}.",
    track_clicks: true,
    track_opens: true,
    client_reference: "ref-{{name}}",
});
```

> **Limit:** A single batch request supports up to 500 recipients. To request a higher limit, contact [support@zeptomail.com](mailto:support@zeptomail.com).

### Send template mail

Uses a pre-built template from your ZeptoMail dashboard. Provide either `template_key` or `template_alias` (or both).

```js
await client.sendMailWithTemplate({
    template_key: "<template key>",
    template_alias: "<template alias>",
    from: { address: "sender@example.com", name: "Sender" },
    to: [{ email_address: { address: "to@example.com", name: "Recipient" } }],
    cc: [{ email_address: { address: "cc@example.com", name: "CC" } }],
    bcc: [{ email_address: { address: "bcc@example.com", name: "BCC" } }],
    reply_to: [{ address: "reply@example.com", name: "Reply" }],
    merge_info: { contact_number: "8787xxxxxx789", company: "example.com" },
    client_reference: "<client reference>",
    mime_headers: { "X-Test": "test" }
});
```

### Send batch template mail

Combines a pre-built template with per-recipient `merge_info` for up to **500 recipients** per request. The same two `merge_info` placement modes available in `sendBatchMail` apply here; per-recipient values inside `to`, with an optional top-level fallback for recipients that don't have their own.

```js
await client.mailBatchWithTemplate({
    template_key: "<template key>",
    template_alias: "<template alias>",
    from: { address: "sender@example.com", name: "Sender" },
    to: [
        {
            email_address: { address: "alice@example.com", name: "Alice" },
            merge_info: { contact: "9600000023", company: "Acme" } // per-recipient
        },
        {
            email_address: { address: "bob@example.com", name: "Bob" },
            // no merge_info — falls back to top-level merge_info below
        }
    ],
    // Top-level merge_info: fallback for recipients without their own
    merge_info: { contact: "0000000000", company: "Default Co." },
    reply_to: [{ address: "reply@example.com", name: "Reply" }],
    client_reference: "<client reference>",
    mime_headers: { "X-Test": "test" }
});
```

> **Limit:** A single batch request supports up to 500 recipients. To request a higher limit, contact [support@zeptomail.com](mailto:support@zeptomail.com).

---
---


> _The clients for managing **Agents**, **Domains**, **Suppression** lists, and **Email Logs** use **OAuth 2.0** rather than a send-mail token. Before using any of these clients, set up an `AuthenticationClient` with the appropriate scope._

## Authentication — one client per scope

Each REST client requires its **own** `AuthenticationClient` instance. ZeptoMail issues **scope-based refresh tokens** — a token that authorises agent operations will not work for domain or suppression operations.

| Client | Specific scopes | All-access scope |
|---|---|---|
| `AgentClient` | `Zeptomail.MailAgents.READ`<br>`Zeptomail.MailAgents.CREATE`<br>`Zeptomail.MailAgents.UPDATE`<br>`Zeptomail.MailAgents.DELETE` | `Zeptomail.MailAgents.All` |
| `DomainClient` | `Zeptomail.Domains.READ`<br>`Zeptomail.Domains.CREATE`<br>`Zeptomail.Domains.UPDATE`<br>`Zeptomail.Domains.DELETE` | `Zeptomail.Domains.All` |
| `SuppressionClient` | `Zeptomail.Suppressions.READ`<br>`Zeptomail.Suppressions.CREATE`<br>`Zeptomail.Suppressions.UPDATE`<br>`Zeptomail.Suppressions.DELETE` | `Zeptomail.Suppressions.All` |
| `EmailLogsClient` | `Zeptomail.email.READ` | — |

Use `*.All` to cover every operation under that client with a single token. Use specific scopes (e.g. `Zeptomail.Domains.READ`) to grant only the minimum privilege needed.

```js
import { AuthenticationClient } from "zeptomail";

// One AuthenticationClient per scope — do NOT share across different clients
const agentAuth = new AuthenticationClient({
    clientId: "<your-client-id>",
    clientSecret: "<your-client-secret>",
    refreshToken: "<agent-scoped-refresh-token>",
    dataCenter: "US", // "US" | "EU" | "IN" | "AU" | "JP" | "CN"
    // Optional:
    timeout: 30_000,  // token-refresh HTTP timeout in ms (default: 30 000)
    bufferMs: 300_000, // refresh token this many ms before expiry (default: 5 min)
    apiVersion: "v1.1", // API version (default: "v1.1")
});

// Separate instances per scope:
const domainAuth     = new AuthenticationClient({ ... });
const suppressionAuth = new AuthenticationClient({ ... });
const emailLogsAuth  = new AuthenticationClient({ ... });
```

Each REST client accepts its `AuthenticationClient` via the constructor, or you can set it later:

```js
const agents = new AgentClient({ authenticationClient: agentAuth });

// Or set after construction:
agents.setAuthenticationClient(agentAuth);
```

### Rotating a refresh token

Call `updateCredentials()` on the existing instance — no need to create a new client:

```js
agentAuth.updateCredentials({ refreshToken: "<new-refresh-token>" });
// The cached access token is automatically invalidated; the next request fetches a fresh one.
```

### Pre-warming the token at startup

Call `authenticate({ force: true })` to fetch a token before the first real request, avoiding latency on the first API call:

```js
await agents.authenticate({ force: true });
```

### Disabling auto-refresh

By default, tokens are refreshed automatically. To manage tokens manually:

```js
const agents = new AgentClient({
    authenticationClient: agentAuth,
    autoAuthenticate: false, // you are responsible for calling refreshAccessToken()
});

await agentAuth.refreshAccessToken(); // call this before making requests
```

---


## 🧩 Agent Management

Manage mail agents (sending identities), their API keys, and SMTP passwords.

```js
import { AgentClient } from "zeptomail";

const agents = new AgentClient({ authenticationClient: agentAuth });
```

### List all agents

> **Required scope:** `Zeptomail.MailAgents.READ` or `Zeptomail.MailAgents.All`

```js
const { data } = await agents.getAllAgents();
// data: Agent[]
```

### Add an agent

> **Required scope:** `Zeptomail.MailAgents.CREATE` or `Zeptomail.MailAgents.All`

```js
const { data } = await agents.addAgent({
    agentName: "My Agent",
    description: "Production sending agent",
});
// data: Agent
```

### Update an agent

> **Required scope:** `Zeptomail.MailAgents.UPDATE` or `Zeptomail.MailAgents.All`

```js
const { data } = await agents.updateAgent({
    agentKey: "<mailagent-key>",
    agentName: "Updated Name",
    description: "Updated description",
});
```

### Delete an agent

> **Required scope:** `Zeptomail.MailAgents.DELETE` or `Zeptomail.MailAgents.All`

```js
await agents.deleteAgent({ agentKey: "<mailagent-key>" });
```

### API Keys

```js
// List API keys
// scope: Zeptomail.MailAgents.READ or .All
const { data } = await agents.getAPIKeys({ agentKey: "<mailagent-key>" });

// Generate a new API key
// scope: Zeptomail.MailAgents.CREATE or .All
const { data } = await agents.generateAPIKey({ agentKey: "<mailagent-key>" });
// data.password contains the plaintext key — store it securely

// Revoke an API key
// scope: Zeptomail.MailAgents.DELETE or .All
await agents.revokeAPIKey({ agentKey: "<mailagent-key>", id: "<api-key-id>" });
```

### SMTP Passwords

```js
// List SMTP passwords
// scope: Zeptomail.MailAgents.READ or .All
const { data } = await agents.getSMTPList({ agentKey: "<mailagent-key>" });

// Generate a new SMTP password
// scope: Zeptomail.MailAgents.CREATE or .All
const { data } = await agents.generateSMTPPassword({ agentKey: "<mailagent-key>" });
// data.password contains the plaintext password — store it securely, it won't be shown again

// Delete an SMTP password
// scope: Zeptomail.MailAgents.DELETE or .All
await agents.deleteSMTPPassword({
    agentKey: "<mailagent-key>",
    passwordId: "<password-id>",
});
```

---

## 🌐 Domain Management

Manage sending domains — add, verify, update, and delete.

```js
import { DomainClient } from "zeptomail";

const domains = new DomainClient({ authenticationClient: domainAuth });
```

### List all domains

> **Required scope:** `Zeptomail.Domains.READ` or `Zeptomail.Domains.All`

```js
const { data } = await domains.getAllDomains();
// data: DomainSummary[]
```

### Get domain details

> **Required scope:** `Zeptomail.Domains.READ` or `Zeptomail.Domains.All`

```js
const { data } = await domains.getDomainDetails({ domainKey: "<domain-key>" });
// data: DomainDetail[]  (includes DKIM and CNAME records)
```

### Add a domain

> **Required scope:** `Zeptomail.Domains.CREATE` or `Zeptomail.Domains.All`

```js
const { data } = await domains.addDomain({
    domainName: "mail.example.com",
    bounceDomainPrefix: "bounce", // forms bounce.mail.example.com
    agentKeys: ["<mailagent-key>"], // optional
});
// data.dkim and data.cname contain the DNS records to configure
```

### Update a domain

> **Required scope:** `Zeptomail.Domains.UPDATE` or `Zeptomail.Domains.All`

```js
const { data } = await domains.updateDomain({
    domainKey: "<domain-key>",
    newDomainName: "mail2.example.com",          // optional
    newBounceDomainPrefix: "bounce2",             // optional
    associateAgents: ["<mailagent-key>"],         // optional
    disassociateAgents: ["<other-mailagent-key>"], // optional
});
```

### Verify a domain

> **Required scope:** `Zeptomail.Domains.UPDATE` or `Zeptomail.Domains.All`

Triggers DNS verification. Configure the DKIM and CNAME records returned from `addDomain` / `getDomainDetails` in your DNS provider first.

```js
const { data } = await domains.verifyDomain({ domainKey: "<domain-key>" });
// data.status will be "verified" if DNS records are correct
```

### Delete a domain

> **Required scope:** `Zeptomail.Domains.DELETE` or `Zeptomail.Domains.All`

```js
await domains.deleteDomain({ domainKey: "<domain-key>" });
```

---

## 🚫 Suppression Management

Manage the Do-Not-Disturb (DND) / suppression list for email addresses and domains.

```js
import { SuppressionClient } from "zeptomail";

const suppression = new SuppressionClient({ authenticationClient: suppressionAuth });
```

### List suppression entries

> **Required scope:** `Zeptomail.Suppressions.READ` or `Zeptomail.Suppressions.All`

```js
const response = await suppression.getSuppressionList({
    dndType: "email", // "email" | "domain"
    // All filters below are optional:
    dateFrom: new Date("2024-01-01"),
    dateTo: "2024-12-31",
    offset: 0,
    limit: 50,
    agentKey: "<mailagent-key>",
    searchValue: "user@example.com",
});
```

### Add suppression entries

> **Required scope:** `Zeptomail.Suppressions.CREATE` or `Zeptomail.Suppressions.All`

```js
await suppression.addSuppressionEntry({
    dndType: "email",
    values: ["blocked@example.com", "spam@test.com"],
    action: "reject",                      // "reject" | "suppress" | "suppress_tracking"
    agentKeys: ["<mailagent-key>"],        // optional — applies to all agents if omitted
    description: "Bounced addresses",      // optional
});
```

> **Note:** `"suppress_tracking"` is only valid when `dndType` is `"email"`.

### Update suppression entries

> **Required scope:** `Zeptomail.Suppressions.UPDATE` or `Zeptomail.Suppressions.All`

```js
await suppression.updateSuppressionEntry({
    dndType: "email",
    values: ["blocked@example.com"],
    action: "suppress",
});
```

### Delete suppression entries

> **Required scope:** `Zeptomail.Suppressions.DELETE` or `Zeptomail.Suppressions.All`

```js
await suppression.deleteSuppressionEntry({
    dndType: "domain",
    values: ["spam.com", "malicious.org"],
});
```

---

## 📋 Email Logs

Query sent email logs and retrieve full details for a specific email.

```js
import { EmailLogsClient } from "zeptomail";

const logs = new EmailLogsClient({ authenticationClient: emailLogsAuth });
```

### List email logs

> **Required scope:** `Zeptomail.email.READ`

All parameters are optional.

```js
const response = await logs.getAllEmailLogs({
    mailAgentKey: "<mailagent-key>",
    requestId: "<request-id>",
    dateFrom: new Date("2024-06-01"),
    dateTo: new Date("2024-06-30"),
    from: "sender@example.com",
    to: "recipient@example.com",
    cc: "cc@example.com",
    bcc: "bcc@example.com",
    recipient: "any@example.com", // matches to / cc / bcc
    subject: "Welcome",
    clientReference: "<client-ref>",
    offset: 0,
    limit: 100,
    isHardBounced: false,
    isSoftBounced: false,
    isMailFailure: false,
    isDelivered: true,
});
```

### Get full details for a single email

> **Required scope:** `Zeptomail.email.READ`

```js
const response = await logs.getEmailDetails({
    emailReference: "<email-reference-id>",
});
```

---

## ⚠️ Error Handling

All clients throw typed errors. Import the classes you need and use `instanceof` to branch:

```js
import { AuthenticationError, NetworkError, TimeoutError, ApiError, ValidationError } from "zeptomail";

try {
    const { data } = await agents.getAllAgents();
} catch (err) {
    if (err instanceof ValidationError) {
        // A required parameter was missing or invalid before the request was sent.
    } else if (err instanceof AuthenticationError) {
        // Token refresh failed — check clientId, clientSecret, refreshToken.
    } else if (err instanceof NetworkError) {
        // Could not reach the server — check connectivity.
    } else if (err instanceof TimeoutError) {
        // The token-refresh or API request timed out.
    } else if (err instanceof ApiError) {
        // The server returned an HTTP error response.
    }
}
```

---

## 📒 Contact Us

Contact us at `support@zeptomail.com` for any issue resolutions or assistance.
