Requirements: Node.js 20 LTS or later
Installation
Or with your preferred package manager:
yarn add ark-email
pnpm add ark-email
bun add ark-email
Quick Start
import Ark from 'ark-email' ;
const client = new Ark ({
apiKey: process . env . ARK_API_KEY ,
});
const email = await client . emails . send ({
from: 'Security <security@myapp.com>' ,
to: [ 'user@example.com' ],
subject: 'Reset your password' ,
html: '<h1>Password Reset</h1><p>Click the link below to reset your password.</p>' ,
text: 'Password Reset \n\n Click the link below to reset your password.' ,
});
console . log ( `Email ID: ${ email . data . id } ` );
console . log ( `Status: ${ email . data . status } ` );
Configuration
Client Options
import Ark from 'ark-email' ;
const client = new Ark ({
apiKey: 'ark_...' , // Required (or set ARK_API_KEY env var)
timeout: 60000 , // Request timeout in ms (default: 60000)
maxRetries: 2 , // Number of retry attempts (default: 2)
baseURL: 'https://api.arkhq.io/v1' , // API base URL (rarely needed)
logLevel: 'info' , // 'off' | 'error' | 'warn' | 'info' | 'debug'
});
Environment Variables
export ARK_API_KEY = "ark_live_..."
export ARK_LOG = "debug" # Enable debug logging
TypeScript Support
The SDK is written in TypeScript and exports all types:
import Ark from 'ark-email' ;
import type {
EmailSendParams ,
SendEmailResponse ,
Domain ,
Webhook ,
} from 'ark-email' ;
const params : EmailSendParams = {
from: 'hello@yourdomain.com' ,
to: [ 'user@example.com' ],
subject: 'Hello!' ,
html: '<p>Hello</p>' ,
};
const response : SendEmailResponse = await client . emails . send ( params );
API Reference
Emails
Send a single email. const email = await client . emails . send ({
from: 'hello@yourdomain.com' ,
to: [ 'recipient@example.com' ],
subject: 'Welcome!' ,
html: '<h1>Hello</h1>' ,
text: 'Hello' , // Optional but recommended
cc: [ 'cc@example.com' ], // Optional
bcc: [ 'bcc@example.com' ], // Optional
replyTo: 'reply@yourdomain.com' , // Optional
tag: 'welcome' , // Optional
metadata: { userId: '123' }, // Optional
trackOpens: true , // Optional
trackClicks: true , // Optional
attachments: [ // Optional
{
filename: 'invoice.pdf' ,
content: base64EncodedContent ,
contentType: 'application/pdf' ,
},
],
headers: { // Optional custom headers
'X-Custom-Header' : 'value' ,
},
});
Returns: SendEmailResponse with data.id, data.status, etc.
Send multiple emails in a single request. const result = await client . emails . sendBatch ({
emails: [
{
from: 'hello@yourdomain.com' ,
to: [ 'user1@example.com' ],
subject: 'Hello User 1' ,
html: '<p>Hello!</p>' ,
},
{
from: 'hello@yourdomain.com' ,
to: [ 'user2@example.com' ],
subject: 'Hello User 2' ,
html: '<p>Hello!</p>' ,
},
],
});
for ( const email of result . data ) {
console . log ( ` ${ email . id } : ${ email . status } ` );
}
Send a raw MIME message. The rawMessage parameter must be base64-encoded . const rawMessage = `From: hello@yourdomain.com
To: user@example.com
Subject: Raw email
Content-Type: text/plain
This is a raw MIME message.` ;
const email = await client . emails . sendRaw ({
from: 'hello@yourdomain.com' ,
to: [ 'user@example.com' ],
rawMessage: Buffer . from ( rawMessage ). toString ( 'base64' ),
});
The rawMessage must be base64-encoded. Pass your RFC 2822 MIME message through Buffer.from(message).toString('base64') before sending.
List emails with filtering and pagination. const emails = await client . emails . list ({
page: 1 ,
perPage: 25 ,
status: 'sent' , // Optional filter
tag: 'welcome' , // Optional filter
});
for ( const email of emails . data ) {
console . log ( ` ${ email . id } : ${ email . subject } - ${ email . status } ` );
}
// Pagination info
console . log ( `Page ${ emails . page } of ${ emails . totalPages } ` );
Get a single email by ID. const email = await client . emails . retrieve ( 'msg_abc123xyz' );
console . log ( `Subject: ${ email . data . subject } ` );
console . log ( `Status: ${ email . data . status } ` );
console . log ( `Created: ${ email . data . createdAt } ` );
Get delivery attempts for an email. const deliveries = await client . emails . getDeliveries ( 'msg_abc123xyz' );
for ( const delivery of deliveries . data ) {
console . log ( `Attempt at ${ delivery . timestamp } : ${ delivery . status } ` );
if ( delivery . error ) {
console . log ( ` Error: ${ delivery . error } ` );
}
}
Retry a failed email. const result = await client . emails . retry ( 'msg_abc123xyz' );
console . log ( `Retry scheduled: ${ result . data . id } ` );
Domains
Register a new sending domain. const domain = await client . domains . create ({
name: 'mail.yourdomain.com' ,
});
console . log ( `Domain ID: ${ domain . data . id } ` );
console . log ( 'DNS Records to configure:' );
for ( const record of domain . data . dnsRecords ) {
console . log ( ` ${ record . type } ${ record . name } -> ${ record . value } ` );
}
List all domains. const domains = await client . domains . list ();
for ( const domain of domains . data ) {
const status = domain . verified ? 'Verified' : 'Pending' ;
console . log ( ` ${ domain . name } : ${ status } ` );
}
Get domain details. const domain = await client . domains . retrieve ( 'dom_abc123' );
console . log ( `Domain: ${ domain . data . name } ` );
console . log ( `Verified: ${ domain . data . verified } ` );
Trigger DNS verification. const result = await client . domains . verify ( 'dom_abc123' );
if ( result . data . verified ) {
console . log ( 'Domain verified successfully!' );
} else {
console . log ( 'DNS records not found. Please check your configuration.' );
}
Remove a domain. await client . domains . delete ( 'dom_abc123' );
console . log ( 'Domain deleted' );
Suppressions
Add an email to the suppression list. const suppression = await client . suppressions . create ({
email: 'bounced@example.com' ,
reason: 'hard_bounce' ,
});
Add multiple emails to the suppression list. const result = await client . suppressions . bulkCreate ({
suppressions: [
{ email: 'bounce1@example.com' , reason: 'hard_bounce' },
{ email: 'complaint@example.com' , reason: 'complaint' },
],
});
console . log ( `Added ${ result . data . createdCount } suppressions` );
List suppressed emails. const suppressions = await client . suppressions . list ({
page: 1 ,
perPage: 50 ,
reason: 'hard_bounce' , // Optional filter
});
for ( const s of suppressions . data ) {
console . log ( ` ${ s . email } : ${ s . reason } (added ${ s . createdAt } )` );
}
Check if an email is suppressed. try {
const suppression = await client . suppressions . retrieve ( 'user@example.com' );
console . log ( `Suppressed: ${ suppression . data . reason } ` );
} catch ( error ) {
if ( error instanceof Ark . NotFoundError ) {
console . log ( 'Email is not suppressed' );
}
throw error ;
}
Remove from suppression list. await client . suppressions . delete ( 'user@example.com' );
console . log ( 'Removed from suppression list' );
Webhooks
Create a webhook endpoint. const webhook = await client . webhooks . create ({
url: 'https://yourapp.com/webhooks/ark' ,
events: [ 'MessageSent' , 'MessageBounced' , 'MessageLoaded' ],
});
console . log ( `Webhook ID: ${ webhook . data . id } ` );
console . log ( `Signing Secret: ${ webhook . data . signingSecret } ` ); // Store this!
List all webhooks. const webhooks = await client . webhooks . list ();
for ( const wh of webhooks . data ) {
console . log ( ` ${ wh . id } : ${ wh . url } ` );
console . log ( ` Events: ${ wh . events . join ( ', ' ) } ` );
}
Update webhook configuration. const webhook = await client . webhooks . update ( 'wh_abc123' , {
events: [ 'MessageSent' , 'MessageBounced' ], // Updated events
});
Send a test event to your webhook. const result = await client . webhooks . test ( 'wh_abc123' , {
event: 'MessageSent' ,
});
console . log ( `Test result: ${ result . data . status } ` );
Delete a webhook. await client . webhooks . delete ( 'wh_abc123' );
Tracking
Configure a custom tracking domain. const tracking = await client . tracking . create ({
domain: 'track.yourdomain.com' ,
});
console . log ( 'Add this CNAME record:' );
console . log ( ` ${ tracking . data . cnameTarget } ` );
List tracking domains. const trackingDomains = await client . tracking . list ();
for ( const t of trackingDomains . data ) {
const status = t . verified ? 'Verified' : 'Pending' ;
console . log ( ` ${ t . domain } : ${ status } ` );
}
Verify tracking domain DNS. const result = await client . tracking . verify ( 'trk_abc123' );
console . log ( `Verified: ${ result . data . verified } ` );
Remove a tracking domain. await client . tracking . delete ( 'trk_abc123' );
Error Handling
The SDK throws typed errors for different scenarios:
import Ark from 'ark-email' ;
const client = new Ark ();
try {
const email = await client . emails . send ({
from: 'hello@yourdomain.com' ,
to: [ 'invalid' ],
subject: 'Test' ,
html: '<p>Test</p>' ,
});
} catch ( error ) {
if ( error instanceof Ark . BadRequestError ) {
console . log ( `Invalid request: ${ error . message } ` );
console . log ( `Error code: ${ error . code } ` );
} else if ( error instanceof Ark . AuthenticationError ) {
console . log ( 'Invalid API key' );
} else if ( error instanceof Ark . RateLimitError ) {
console . log ( `Rate limited. Retry after: ${ error . headers ?.[ 'retry-after' ] } ` );
} else if ( error instanceof Ark . APIConnectionError ) {
console . log ( 'Network error - check your connection' );
} else if ( error instanceof Ark . APIError ) {
console . log ( `API error ${ error . status } : ${ error . message } ` );
}
throw error ;
}
Error Types
Error Class HTTP Status Description BadRequestError400 Invalid request parameters AuthenticationError401 Invalid or missing API key PermissionDeniedError403 Insufficient permissions NotFoundError404 Resource not found UnprocessableEntityError422 Validation error RateLimitError429 Too many requests InternalServerError5xx Server error APIConnectionError— Network/connection issue
Advanced Usage
Raw Response Access
Access HTTP headers, status codes, and raw response data:
// Method 1: asResponse() - returns raw Response
const response = await client . emails . send ({ ... }). asResponse ();
console . log ( response . status );
console . log ( response . headers );
// Method 2: withResponse() - returns both parsed data and response
const { data : email , response : raw } = await client . emails . send ({ ... }). withResponse ();
console . log ( email . data . id );
console . log ( raw . headers . get ( 'x-request-id' ));
Custom Fetch
Provide your own fetch implementation for proxies or custom behavior:
import Ark from 'ark-email' ;
import { fetch as undiciFetch } from 'undici' ;
const client = new Ark ({
fetch: undiciFetch ,
});
Request Options
Pass per-request options:
const email = await client . emails . send (
{
from: 'hello@yourdomain.com' ,
to: [ 'user@example.com' ],
subject: 'Test' ,
html: '<p>Test</p>' ,
},
{
timeout: 10000 , // Override timeout for this request
maxRetries: 0 , // Disable retries for this request
headers: {
'X-Custom-Header' : 'value' ,
},
}
);
Idempotency
Use idempotency keys to safely retry requests:
import { randomUUID } from 'crypto' ;
const idempotencyKey = randomUUID ();
// This request can be safely retried with the same key
const email = await client . emails . send ({
from: 'hello@yourdomain.com' ,
to: [ 'user@example.com' ],
subject: 'Order Confirmation' ,
html: '<p>Your order is confirmed.</p>' ,
idempotencyKey ,
});
Custom Logger
Use your preferred logging library:
import Ark from 'ark-email' ;
import pino from 'pino' ;
const logger = pino ();
const client = new Ark ({
logger: logger . child ({ service: 'ark' }),
});
Supported loggers: pino, winston, bunyan, consola, signale, @std/log.
Accessing Undocumented Endpoints
Make requests to endpoints not yet in the SDK:
const result = await client . post ( '/some/new/endpoint' , {
body: { key: 'value' },
query: { param: 'value' },
});
Framework Integration
Express
import express from 'express' ;
import Ark from 'ark-email' ;
const app = express ();
const ark = new Ark ();
app . post ( '/api/send-email' , async ( req , res ) => {
try {
const email = await ark . emails . send ({
from: 'noreply@yourapp.com' ,
to: [ req . body . to ],
subject: req . body . subject ,
html: req . body . html ,
});
res . json ({ emailId: email . data . id });
} catch ( error ) {
if ( error instanceof Ark . APIError ) {
res . status ( error . status ?? 500 ). json ({ error: error . message });
}
throw error ;
}
});
Next.js (App Router)
// app/api/send-email/route.ts
import { NextResponse } from 'next/server' ;
import Ark from 'ark-email' ;
const ark = new Ark ();
export async function POST ( request : Request ) {
const { to , subject , html } = await request . json ();
try {
const email = await ark . emails . send ({
from: 'noreply@yourapp.com' ,
to: [ to ],
subject ,
html ,
});
return NextResponse . json ({ emailId: email . data . id });
} catch ( error ) {
if ( error instanceof Ark . APIError ) {
return NextResponse . json (
{ error: error . message },
{ status: error . status ?? 500 }
);
}
throw error ;
}
}
Hono
import { Hono } from 'hono' ;
import Ark from 'ark-email' ;
const app = new Hono ();
const ark = new Ark ();
app . post ( '/send-email' , async ( c ) => {
const { to , subject , html } = await c . req . json ();
const email = await ark . emails . send ({
from: 'noreply@yourapp.com' ,
to: [ to ],
subject ,
html ,
});
return c . json ({ emailId: email . data . id });
});
export default app ;
Fastify
import Fastify from 'fastify' ;
import Ark from 'ark-email' ;
const fastify = Fastify ();
const ark = new Ark ();
fastify . post ( '/send-email' , async ( request , reply ) => {
const { to , subject , html } = request . body as any ;
const email = await ark . emails . send ({
from: 'noreply@yourapp.com' ,
to: [ to ],
subject ,
html ,
});
return { emailId: email . data . id };
});
fastify . listen ({ port: 3000 });
NestJS
// ark.service.ts
import { Injectable } from '@nestjs/common' ;
import Ark from 'ark-email' ;
@ Injectable ()
export class ArkService {
private client : Ark ;
constructor () {
this . client = new Ark ();
}
async sendEmail ( to : string , subject : string , html : string ) {
return this . client . emails . send ({
from: 'noreply@yourapp.com' ,
to: [ to ],
subject ,
html ,
});
}
}
// email.controller.ts
import { Controller , Post , Body } from '@nestjs/common' ;
import { ArkService } from './ark.service' ;
@ Controller ( 'email' )
export class EmailController {
constructor ( private arkService : ArkService ) {}
@ Post ( 'send' )
async send (@ Body () body : { to : string ; subject : string ; html : string }) {
const email = await this . arkService . sendEmail ( body . to , body . subject , body . html );
return { emailId: email . data . id };
}
}
Resources