Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@brew.new/sdk

Official TypeScript SDK for the Brew public API.

  • Resource-oriented surface — brew.contacts.upsert(...) instead of raw HTTP plumbing.
  • One typed error class (BrewApiError) for every non-2xx path, and typed transport errors (BrewTimeoutError, BrewConnectionError) when no answer arrives.
  • A deadline (timeoutMs) and cancellation (signal) that cover the whole request, response body included.
  • Safe retries with exponential backoff + jitter.
  • Auto-generated Idempotency-Key on POST so retries never double-write.
  • Node 20+, server-first. API keys are secrets — do not use this SDK directly in a browser.

Install

bun add @brew.new/sdk
# or
npm install @brew.new/sdk

Quick start

import { createBrewClient } from '@brew.new/sdk'

const brew = createBrewClient({
  apiKey: process.env.BREW_API_KEY!,
})

const contact = await brew.contacts.upsert({
  email: 'jane@example.com',
  firstName: 'Jane',
  customFields: { plan: 'enterprise' },
})

const found = await brew.contacts.get('jane@example.com')

const { data } = await brew.contacts.search({
  filters: [
    { field: 'customFields.plan', operator: 'equals', value: 'enterprise' },
  ],
})

That's the whole shape. Every other method follows the same pattern: list(query) for a page, get(id) for the bare row, and a 404 — not an empty page — when the id is unknown.

Upgrading from 10.x? 11.0.0 turns emails.previewClients into a rendering job you poll with emails.getClientPreview, and drops the deprecated verificationStatus mirror on contacts. From 9.x? 10.0.0 tracks the public API v1 cleanup. Read the CHANGELOG for both migrations.

Images: upload, add, delete

The brand image library is what the email agent searches when it designs. Adding and deleting images is free.

import { openAsBlob } from 'node:fs'

// A local file, in one call: opens an upload, sends the bytes, adds it.
const logo = await brew.content.uploadImage({
  file: await openAsBlob('./assets/logo.png'), // or a Buffer / ArrayBuffer
  fileName: 'logo.png', // contentType inferred from the extension
})
// → { url: 'https://cdn.brew.new/…', width, height, aspectRatio, assetId }

// A public URL (or up to 100 with `imageUrls`, imported in the background).
const hero = await brew.content.addImage({
  imageUrl: 'https://acme.com/hero.jpg',
})

// Remove one from the library. Emails already using it keep rendering.
await brew.brand.deleteImage(hero.assetId) // → { assetId, deleted: true }

uploadImage takes a Blob/File, an ArrayBuffer or a Uint8Array up to 20 MB (2 MB for SVG) as PNG, JPEG, GIF, WebP, AVIF, TIFF or SVG. Pass contentType when the file name has no such extension. An empty file, or one over those limits, throws a TypeError before any request. The bytes go straight to the upload's own URL without your API key, and that step is retried like any other (a repeat never replaces the first file). Opening the upload is not retried by default, because the API never replays it and each retry would hold one of the brand's 20 upload slots for 15 minutes; if it fails, call uploadImage again. To drive the steps yourself, call brew.content.createImageUpload({ fileName, contentType, size }), POST the raw bytes to its uploadUrl within 15 minutes, then brew.content.addImage({ uploadId }).

Pointing at a different environment

baseUrl is configurable. By default it points at production (https://brew.new/api), but you can override it for staging, local development, or a custom proxy:

const brew = createBrewClient({
  apiKey: process.env.BREW_API_KEY!,
  baseUrl: process.env.BREW_API_URL ?? 'https://brew.new/api',
})

Common values:

  • https://brew.new/api — production (default)
  • https://staging.brew.new/api — staging
  • http://localhost:3000/api — your own dev server

Trailing slashes are normalized either way. See docs/configuration.md for the full list of config options (timeoutMs, maxRetries, retryOnTimeout, signal, userAgent, custom fetch, etc.).

Documentation

Topic File
Analytics resource docs/analytics.md
Automations resource docs/automations.md
Audiences resource docs/audiences.md
Brand resource docs/brand.md
Brands (lifecycle) docs/brands.md
Chats resource docs/chats.md
Client configuration docs/configuration.md
Contacts resource docs/contacts.md
Domains resource docs/domains.md
Emails resource (+ send) docs/emails.md
Fields resource docs/fields.md
Insights resource docs/insights.md
Notifications resource docs/notifications.md
Error handling docs/errors.md
Retries + idempotency docs/retries-and-idempotency.md
Sends resource (reads + lifecycle) docs/sends.md
Templates resource docs/templates.md
Flows resource docs/flows.md
Development + OpenAPI sync docs/development.md
Releasing a new version RELEASING.md

Note: brew.brand.* (singular) reads the brand the CURRENT request acts on — its design system, identity, and extraction readiness. brew.brands.* (plural) is the organization-level lifecycle: list, create, and poll brands.

An API key is scoped either to ONE BRAND or to the whole ORGANIZATION. A brand-scoped key resolves its brand automatically. An organization-scoped key must name one per request — pass brandId in the client config, or pin one with client.withBrand(id). There is no default brand: omitting it returns 400 BRAND_ID_REQUIRED.

const brew = createBrewClient({ apiKey: process.env.BREW_API_KEY! })
const { data: brands } = await brew.brands.list()
const acme = brew.withBrand(brands[0].brandId)
await acme.emails.list()

Development

Use a modern Node 20 or newer runtime for SDK development. On this machine, Vitest worked with Node 20.19.2 and newer, but failed on the older system Node 20.10.0.

bun install
bun tsc         # typecheck
bun lint        # eslint
bun run format  # prettier
bun run test    # vitest — NOT `bun test`, that hits Bun's built-in runner and bypasses MSW setup
bun run build   # tsup: dist/ with esm + cjs + .d.ts

Full contribution + testing conventions live in AGENTS.md. The big one: every change is driven red → green via vitest + MSW, no exceptions for "trivial" pure functions.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages