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 <[email protected] >' ,
to: [ '[email protected] ' ],
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: '[email protected] ' ,
to: [ '[email protected] ' ],
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: '[email protected] ' ,
to: [ '[email protected] ' ],
subject: 'Welcome!' ,
html: '<h1>Hello</h1>' ,
text: 'Hello' , // Optional but recommended
cc: [ '[email protected] ' ], // Optional
bcc: [ '[email protected] ' ], // Optional
replyTo: '[email protected] ' , // Optional
tags: [ 'welcome' , 'onboarding' ], // Optional
metadata: { userId: '123' }, // Optional
trackOpens: true , // Optional
trackClicks: true , // Optional
scheduledAt: '2024-01-20T09:00:00Z' , // Optional - ISO 8601
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: '[email protected] ' ,
to: [ '[email protected] ' ],
subject: 'Hello User 1' ,
html: '<p>Hello!</p>' ,
},
{
from: '[email protected] ' ,
to: [ '[email protected] ' ],
subject: 'Hello User 2' ,
html: '<p>Hello!</p>' ,
},
],
});
for ( const email of result . data ) {
console . log ( ` ${ email . id } : ${ email . status } ` );
}
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: '[email protected] ' ,
reason: 'hard_bounce' ,
});
Add multiple emails to the suppression list. const result = await client . suppressions . bulkCreate ({
suppressions: [
{ email: '[email protected] ' , reason: 'hard_bounce' },
{ email: '[email protected] ' , 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 ( '[email protected] ' );
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 ( '[email protected] ' );
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: '[email protected] ' ,
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: '[email protected] ' ,
to: [ '[email protected] ' ],
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: '[email protected] ' ,
to: [ '[email protected] ' ],
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: '[email protected] ' ,
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: '[email protected] ' ,
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: '[email protected] ' ,
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: '[email protected] ' ,
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: '[email protected] ' ,
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