Skip to content

jcodog/mailchannels

Repository files navigation

@jconet-ltd/mailchannels-client

Builds Coverage

TypeScript-first wrapper around the MailChannels Email API with runtime validation, DKIM enforcement, and ergonomic helpers. The client targets Node.js 18+ and reuses the built-in Fetch API, so there are no production dependencies.

ℹ️ Before sending email you must provision an SMTP password, generate an API key with the api scope, and configure Domain Lockdown records as documented by MailChannels.1

Table of contents

Installation

# pnpm
pnpm add @jconet-ltd/mailchannels-client

# npm
npm install @jconet-ltd/mailchannels-client

# Yarn
yarn add @jconet-ltd/mailchannels-client

# Bun
bun add @jconet-ltd/mailchannels-client

Quick start

import { MailChannelsClient } from "@jconet-ltd/mailchannels-client";

const client = new MailChannelsClient({
  apiKey: process.env.MAILCHANNELS_API_KEY ?? "",
  dkim: {
    domain: "example.com",
    selector: "mcdkim",
    privateKey: process.env.MAILCHANNELS_DKIM_PRIVATE_KEY ?? "",
  },
});

await client.sendEmail({
  personalizations: [
    {
      to: [{ email: "recipient@example.net", name: "Sakura Tanaka" }],
    },
  ],
  from: { email: "sender@example.com", name: "Priya Patel" },
  subject: "Testing Email API",
  content: [
    { type: "text/plain", value: "Hi Sakura. This is just a test from Priya." },
    {
      type: "text/html",
      value: "<p>Hi Sakura.<br>This is just a test from Priya.</p>",
    },
  ],
});

The client injects the DKIM defaults into both the request body and every personalization, ensuring compliance with MailChannels' DKIM requirements even when you do not set them manually.

Under the hood the client issues a POST request to https://api.mailchannels.net/tx/v1/send with your API key in the X-Api-Key header, exactly as described in the MailChannels sending guide.2

Features

  • Required DKIM configuration enforced at construction time to keep every request compliant.3
  • Strong TypeScript definitions that mirror the MailChannels /send payload.5
  • Runtime validation of key fields (personalizations, content, DKIM) before the network hop.
  • Optional dry-run support (?dry-run=true) so you can validate payloads without delivering mail.6
  • Detailed error metadata surfaced via MailChannelsError, including request identifiers, retry hints, headers, and the original payload.
  • Attachment helper validation around MIME type, filename, and Base64 encoding.7
  • Built-in Fetch integration with an escape hatch for custom implementations (tests, polyfills).

MailChannels setup guide

  1. Create an account. Sign up for MailChannels, verify your email address, and add valid billing details as prompted.1

  2. Complete the authentication prerequisites. From the console create an SMTP password, generate an API key with the api scope, and publish Domain Lockdown (_mailchannels) TXT records for every sending domain.2

  3. Provision DKIM. MailChannels requires DKIM signatures for modern deliverability. You can:

    • Generate a private key, derive the public key, and publish it at selector._domainkey.yourdomain. Sample OpenSSL commands:3

      openssl genrsa 2048 | tee priv_key.pem \
        | openssl rsa -outform der \
        | openssl base64 -A > priv_key.txt
      
      echo -n "v=DKIM1;p=" > pub_key_record.txt
      openssl rsa -in priv_key.pem -pubout -outform der \
        | openssl base64 -A >> pub_key_record.txt

      Publish the contents of pub_key_record.txt as a TXT record at <selector>._domainkey.<yourdomain>.

    • Or call the MailChannels DKIM APIs (POST /tx/v1/domains/{domain}/dkim-keys, etc.) to generate and activate key pairs directly, then publish the returned DNS record.3

  4. Record your defaults. Capture the following details for the domain you will sign with:

    • dkim_domain – typically the same domain as your From address for DMARC alignment.
    • dkim_selector – the label you used in DNS (for example mcdkim).
    • dkim_private_key – the Base64-encoded private key (contents of priv_key.txt if you used the OpenSSL recipe above).

Once the DNS changes propagate you are ready to send signed traffic through the /send endpoint.4

Per-recipient overrides

You can still customise DKIM (and other headers) per recipient. Values defined inside a personalization override the client defaults for that specific message.

import { MailChannelsClient } from "@jconet-ltd/mailchannels-client";
import type { SendEmailRequest } from "@jconet-ltd/mailchannels-client";

const client = new MailChannelsClient({
  apiKey: "YOUR-API-KEY",
  dkim: {
    domain: "example.com",
    selector: "mcdkim",
    privateKey: "BASE64_PRIVATE_KEY",
  },
});

const message: SendEmailRequest = {
  personalizations: [
    {
      to: [{ email: "banana-lover123@example.com" }],
      subject: "BANANAS ARE ON SALE",
      dynamic_template_data: { discountCode: "BANANA-BOAT" },
    },
    {
      to: [{ email: "vip@example.com" }],
      subject: "Exclusive VIP Pricing",
      dkim_selector: "vipselector",
      dkim_private_key: "BASE64_VIP_KEY",
    },
  ],
  from: { email: "news@example.com", name: "Example News" },
  template_id: "spring-sale",
  content: [
    {
      type: "text/plain",
      value: "Plain-text fallback for clients that do not render HTML.",
    },
    {
      type: "text/html",
      value:
        "<html><body><p>Check the sale in your personalized template.</p></body></html>",
    },
  ],
  metadata: { campaign: "spring-2025" },
};

await client.sendEmail(message, { dryRun: true });

Attachments

Attachments must be Base64 encoded and accompanied by a MIME type plus filename. The client checks these fields before sending.

import { MailChannelsClient } from "@jconet-ltd/mailchannels-client";
import { promises as fs } from "node:fs";

const client = new MailChannelsClient({
  apiKey: "YOUR-API-KEY",
  dkim: {
    domain: "example.com",
    selector: "mcdkim",
    privateKey: "BASE64_PRIVATE_KEY",
  },
});

const logoPngBase64 = Buffer.from(
  await fs.readFile("./assets/logo.png")
).toString("base64");

await client.sendEmail({
  personalizations: [{ to: [{ email: "recipient@example.com" }] }],
  from: { email: "sender@example.com" },
  subject: "Email with Attachment",
  content: [{ type: "text/plain", value: "Please see the attached image." }],
  attachments: [
    {
      type: "image/png",
      filename: "logo.png",
      content: logoPngBase64,
    },
  ],
});

Error handling

API failures throw a MailChannelsError with rich diagnostics so you can log or retry intelligently. In addition to the message and status code, the error exposes the HTTP status text, response headers, any structured body, the upstream request identifier, and a parsed retryAfterSeconds hint when the service returns Retry-After.

import { MailChannelsError } from "@jconet-ltd/mailchannels-client";

try {
  await client.sendEmail(payload);
} catch (error) {
  if (error instanceof MailChannelsError) {
    console.error(
      "MailChannels request failed",
      error.status,
      error.statusText,
      error.requestId
    );

    if (error.retryAfterSeconds) {
      console.info("Safe to retry in", error.retryAfterSeconds, "seconds");
    }

    console.debug("Response headers", error.headers);
    console.debug("Original payload", error.details);
  }
  throw error;
}

Testing

npm install
npm run typecheck
npm run build

npm run build emits ESM output plus .d.ts bundles into dist/ ready for publishing.

API surface

new MailChannelsClient(options)

  • apiKey – MailChannels API credential with the api scope.2
  • dkim – required defaults { domain, selector, privateKey }; applied automatically to every request and personalization.3
  • baseUrl – override the API endpoint (defaults to https://api.mailchannels.net/tx/v1/).
  • fetchImplementation – provide an alternative Fetch-compatible function if needed.
  • defaultHeaders – headers merged into every outbound request.

sendEmail(payload, options?)

  • Accepts a strongly typed payload matching the MailChannels /send schema.5
  • Ensures DKIM, recipients, and content blocks are valid before calling the API.
  • options.dryRun toggles the dry-run query to request synchronous validation.6
  • options.signal lets you cancel in-flight requests with an AbortSignal.
  • options.idempotencyKey sets the Idempotency-Key header for safe retries.

Errors

Non-success responses raise MailChannelsError, exposing the HTTP status, status text, request identifier, retry hints, headers, and the parsed or raw response body. Advanced callers can construct the error manually with MailChannelsErrorOptions when wrapping lower-level utilities.

Footnotes

  1. MailChannels account creation
  2. Authentication prerequisites and Domain Lockdown
  3. DKIM setup and field requirements
  4. Sending emails with the /send endpoint
  5. Understanding the /send payload structure
  6. Dry-run behaviour in the API reference
  7. Attachment requirements and limitations

About

Lightweight TypeScript wrapper for the MailChannels Email API.

Resources

Stars

Watchers

Forks

Packages

No packages published