Appearance
JavaScript SDK
marleyfetch-sdk wraps the API in a chainable builder. It has no dependencies, uses the runtime's native fetch, and weighs about 1 kB gzipped, so it runs anywhere fetch does: Node.js 18+, Cloudflare Workers, Vercel and Netlify functions, Deno and Bun.
bash
npm install marleyfetch-sdkSend an email
ts
import { MarleyFetch } from 'marleyfetch-sdk'
const mf = new MarleyFetch({ apiKey: process.env.MARLEYFETCH_API_KEY })
const { messageId } = await mf
.from('you@yourcompany.com')
.to('alice@example.com', 'bob@example.com')
.cc('manager@example.com')
.subject('Monthly report')
.text_body('Numbers are up.')
.html_body('<p>Numbers are up.</p>')
.send()Every setter is named after the API field it sets and returns the builder, so you can call them in any order. Nothing is sent until the chain ends with:
| Method | Calls | Returns |
|---|---|---|
.send() | POST /v1/send | after delivery, with messageId |
.enqueue() | POST /v1/enqueue (Pro) | as soon as the email is queued |
.build() | nothing | the request body — handy in tests |
to, cc and bcc accept several arguments, arrays or comma-separated strings, and add up across calls.
Send a batch
ts
const { batch_id } = await mf.batch()
.name('Renewal reminder')
.template_id(3) // or .subject() / .text_body() / .html_body()
.audience_id(12) // or .recipients(['a@example.com', ...])
.send()
const { batch } = await mf.getBatch(batch_id)
console.log(batch.status, `${batch.sent_count}/${batch.total_count}`)
await mf.deleteBatch(batch_id) // once it is completed or failedSee Batch sending for how batches pace themselves and personalise content.
Handle errors
Any non-2xx response throws a MarleyFetchError:
ts
import { MarleyFetch, MarleyFetchError } from 'marleyfetch-sdk'
try {
await mf.to('a@example.com').subject('Hi').text_body('Hi').send()
} catch (error) {
if (error instanceof MarleyFetchError) {
console.error(error.status, error.code, error.message)
if (error.code === 'RATE_LIMITED' && error.retryAfter) {
// wait error.retryAfter seconds, then retry
}
}
throw error
}error.code is one of the codes in Errors. Network failures throw whatever the runtime's fetch throws. An incomplete email (no recipient, no subject, no body) throws a plain Error from .build() before any request is made.
The SDK does not send an Idempotency-Key yet. If you need safe retries on /send, call the API with fetch directly.
Reading the API key
| Runtime | |
|---|---|
| Node.js, Vercel, Netlify | process.env.MARLEYFETCH_API_KEY |
| Cloudflare Workers | the env argument: env.MARLEYFETCH_API_KEY (set with wrangler secret put) |
| Deno | Deno.env.get('MARLEYFETCH_API_KEY'), and import from npm:marleyfetch-sdk |
| Bun | Bun.env.MARLEYFETCH_API_KEY |
On Cloudflare Workers, create the client inside the handler, where env is available:
ts
import { MarleyFetch } from 'marleyfetch-sdk'
export default {
async fetch(request: Request, env: { MARLEYFETCH_API_KEY: string }) {
const mf = new MarleyFetch({ apiKey: env.MARLEYFETCH_API_KEY })
await mf.to('ops@yourcompany.com').subject('Worker alert').text_body('Something happened').send()
return new Response('sent')
},
}Smaller imports
MarleyFetch pulls in both builders. If you only send single emails, import the pieces (about 800 bytes gzipped):
ts
import { createTransport } from 'marleyfetch-sdk/core'
import { EmailBuilder } from 'marleyfetch-sdk/email'
const transport = createTransport({ apiKey: process.env.MARLEYFETCH_API_KEY })
await new EmailBuilder(transport).to('a@example.com').subject('Hello').text_body('Hi').send()createTransport returns a plain (method, path, body) => Promise function, which is easy to stub in tests. The package is ESM-only.