Skip to main content

Current Client Surface

The TypeScript SDK is resource-oriented.
import { createBrewClient } from '@brew.new/sdk'

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

Resources And Methods

ResourceMethods
brew.analyticscampaigns(), automations(), events(), eventsAll()
brew.analytics.sendslist(), listAll()
brew.analytics.triggerInstanceslist(), listAll()
brew.audienceslist(), create(), update(), delete()
brew.automationscreate(), list(), patch(), publish() / unpublish() (convenience wrappers over patch({ published })), delete(), test()
brew.automations.triggerscreate(), list(), patch(), delete(), fire()
brew.automations.runslist(), listAll()
brew.brandget() ({ include } embeds identity / emailDesign / imageStyle / logos), patch() / update(), getImages({ q?, type?, aspectRatio? })
brew.contactsupsert(), upsertMany(), patch(), delete(), deleteMany(), search(), searchAll(), validate(), importCsv()
brew.contentgenerateImage(), gif({ from }), transform({ operation }), htmlToPng(), addImage()
brew.domainslist(), add(), verify(), updateSettings(), delete()
brew.emailslist(), generate(), import(), edit(), auditAccessibility(), restore(), delete(), send()
brew.sendscancel()
brew.fieldslist(), create(), delete()
brew.helpget()
brew.templateslist()
brew.usageget()
Reads are flat: every resource has exactly one list(). Pass the resource’s id key for a single row, and include to embed heavier detail:
ReadSingle-row forminclude opt-ins
brew.emails.list()list({ emailId }) (carries previewImage)html, versions
brew.audiences.list()list({ audienceId })count
brew.automations.list()list({ automationId })graph, versions
brew.automations.runs.list()list({ automationRunId })logs
brew.automations.triggers.list()list({ triggerEventId })
brew.analytics.triggerInstances.list()list({ triggerInstanceId })
brew.analytics.sends.list()list({ sendId }) (also filter by { emailId })events
brew.domains.list()list({ domainId }) (or { sendableOnly: true })
The contact read is brew.contacts.search({ filters, audienceId?, search?, sort, count?, cursor }) — look up one address with a { field: 'email', operator: 'equals', value } filter. The public SDK surface is deterministic-onlybrew.automations and brew.automations.triggers do not expose AI authoring methods. AI body generation is still available on brew.emails.generate({ prompt }); chain it with brew.automations.create({ … }) to assemble automations programmatically. To ingest existing markup as an editable design, use brew.emails.import({ format, content }). Every list method accepts { limit, cursor } and returns the uniform { data, pagination } envelope — loop while (pagination.cursor !== null). contacts.searchAll, analytics.sends.listAll, automations.runs.listAll, analytics.triggerInstances.listAll, and analytics.eventsAll are async iterators that page through the whole result set for you via the shared autoPaginate helper. brew.brand.get() is read-only — there is no brand management resource. It returns the single brand bound to your API key plus its extraction readiness. brew.help.get() hits GET /v1/help — a no-auth, machine-readable catalog (auth, scopes, rate limits, per-operation credit metering, the error envelope, and the full endpoint list) any MCP server or agent can parse to self-discover the API.

Common Flow — Trigger → Emails → Automation → Publish → Fire

End-to-end deterministic recipe: create a custom trigger, mint each email body in parallel, assemble the graph referencing those emailIds, publish, and fire. Every step returns a typed result.
import { createBrewClient } from '@brew.new/sdk'

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

// 1. Create the trigger (deterministic shape — provider is hardcoded brew_api).
//    Create returns the bare trigger row (HTTP 201).
const trigger = await brew.automations.triggers.create({
  title: 'Subscription Renewed',
  description: 'Fires when a subscription is renewed.',
  payloadSchema: {
    type: 'object',
    fields: [
      { key: 'email', type: 'string', required: true },
      { key: 'plan', type: 'string', required: true },
      { key: 'amount', type: 'int', required: true },
    ],
  },
})

// 2. Pre-mint each email body. These rows are referenced by sendEmail
//    nodes inside the automation graph.
const [welcome, gettingStarted] = await Promise.all([
  brew.emails.generate({
    prompt: 'Thank-you email for a successful renewal.',
  }),
  brew.emails.generate({
    prompt:
      'Getting-started tips for renewed subscribers — focus on the Pro features.',
  }),
])

// 3. Assemble the automation graph; sendEmail nodes reference the
//    pre-minted emailIds + emailVersionIds.
const automation = await brew.automations.create({
  name: 'Renewal welcome flow',
  triggerEventId: trigger.triggerEventId,
  nodes: [
    {
      id: 'trg',
      label: 'On renewal',
      type: 'trigger',
      config: { triggerEventId: trigger.triggerEventId },
    },
    {
      id: 'send_welcome',
      label: 'Welcome',
      type: 'sendEmail',
      config: {
        emailId: welcome.emailId,
        emailVersionId: welcome.emailVersionId,
        domainId: 'dom_brand_primary',
        subject: 'Welcome back, {{firstName | there}}!',
        previewText: 'Quick thanks + 2 things to try first.',
      },
    },
    {
      id: 'wait_2d',
      label: 'Wait 2 days',
      type: 'wait',
      config: { duration: 2, unit: 'days' },
    },
    {
      id: 'send_getting_started',
      label: 'Getting started',
      type: 'sendEmail',
      config: {
        emailId: gettingStarted.emailId,
        emailVersionId: gettingStarted.emailVersionId,
        domainId: 'dom_brand_primary',
        subject: 'Getting started with Pro',
        previewText: 'Three feature tips for your first week.',
      },
    },
  ],
  connections: [
    { from: 'trg', to: 'send_welcome' },
    { from: 'send_welcome', to: 'wait_2d' },
    { from: 'wait_2d', to: 'send_getting_started' },
  ],
})

// 4. Publish (convenience wrapper over patch({ published: true })).
await brew.automations.publish({ automationId: automation.automationId })

// 5. Fire the trigger with a real payload.
const fire = await brew.automations.triggers.fire({
  triggerEventId: trigger.triggerEventId,
  payload: {
    email: 'jane@example.com',
    plan: 'Pro',
    amount: 4200,
  },
  idempotencyKey: 'renewal-jane-2026-04-08',
})
console.log('Started automation runs:', fire.details?.automationRunIds)

// 6. Poll the run lifecycle (reads live under automations.runs).
const { data: runs } = await brew.automations.runs.list({
  automationRunId: fire.details!.automationRunIds[0]!,
  include: 'logs',
})
console.log('Status:', runs[0]!.status)

Reading sends + trigger instances

Send delivery splits in two: the write is brew.emails.send(input) (pass test: true for a one-off QA send), and every read lives on brew.analytics.sends.list() — one flat read, identity in the query. To pull back a scheduled or queued send before it goes out, call brew.sends.cancel(sendId).
// Send an email design to a target (write).
const accepted = await brew.emails.send({
  emailId: 'eml_x',
  domainId: 'dom_y',
  audienceId: 'aud_z',
  subject: 'Launch update',
  scheduledAt: '2026-07-01T09:00:00Z',
})

// Pull it back before it goes out (idempotent — already-canceled is a no-op).
const { status } = await brew.sends.cancel(accepted.sendId) // → 'canceled'

// Poll its lifecycle + stats, with the per-recipient event feed inlined (read).
const { data: sends } = await brew.analytics.sends.list({
  sendId: accepted.sendId,
  include: 'events',
})
const send = sends[0]!
console.log(send.status, send.stats?.delivered)
for (const ev of send.events ?? []) {
  console.log(ev.eventType, ev.recipientEmail)
}

// All sends for one design.
const { data: forEmail } = await brew.analytics.sends.list({ emailId: 'eml_x' })

// Fired-trigger instances (the audit trail of every trigger fire).
const instances = await brew.analytics.triggerInstances.list({ triggerEventId: 'tri_signup' })

AutomationNodeInput — per-kind discriminated union

AutomationNodeInput is a discriminated union by type; setting node.type narrows node.config automatically. The five node kinds map 1:1 to the server-side Zod schemas:
node.typenode.config shape
trigger{ triggerEventId? }
sendEmail{ emailId, emailVersionId, domainId, subject, previewText } (all five required)
wait{ duration, unit: 'minutes' | 'hours' | 'days' | 'weeks' }
filter{ logicalOperator, conditions[] }
split{ mode: 'percentage' | 'condition', … }
Each sendEmail node’s subject / previewText support {{variable | fallback}} interpolation against the trigger payload.

Source Of Truth

The SDK follows the Brew OpenAPI contract. If you want the raw HTTP shape behind any method, use the API reference in this docs site.

Need Help?

Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:

Search Documentation

Type in the “Ask any question” search bar at the top left to instantly find relevant documentation pages.

ChatGPT/Claude Integration

Click “Open in ChatGPT” at the top right of any page to analyze documentation with ChatGPT or Claude for deeper insights.