Skip to main content

Create The Client

Get an API key from brew.new/settings/api. Each key is bound to one brand at creation — pick the brand you want this client to act on before generating.
import { createBrewClient } from '@brew.new/sdk'

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

1. Upsert A Contact

const contactResult = await brew.contacts.upsert({
  email: 'john@example.com',
  firstName: 'John',
  lastName: 'Doe',
  subscribed: true,
  customFields: {
    plan: 'enterprise',
  },
})

console.log(contactResult.contact.email)
console.log(contactResult.created)

2. Browse Public Templates (optional)

const { templates } = await brew.templates.list({
  brand: 'vercel.com',
})
Templates are public reference emails. Pass any one as referenceEmailId to email generation when you want to anchor the output on its layout. Your own brand design context is automatic — every API key is bound to one brand at creation, and brew.emails.generate grounds itself in that brand without you ever passing a brandId.

3. Generate An Email

const generated = await brew.emails.generate({
  prompt: 'Create a welcome email for new subscribers',
  referenceEmailId: templates[0]?.emailId,
})

if ('emailId' in generated) {
  console.log(generated.emailId)
  console.log(generated.emailHtml)
} else {
  console.log(generated.response)
}

4. Edit A Saved Email (optional)

brew.emails.edit runs the agent against an existing email’s current latest JSX and persists a new version: "latest" row on the same emailId, demoting the previous head to a numeric historical version.
if ('emailId' in generated) {
  const edited = await brew.emails.edit({
    emailId: generated.emailId,
    prompt: 'Tighten the headline and add a friendlier sign-off.',
  })

  if ('emailId' in edited) {
    console.log(edited.emailId, edited.emailHtml)
  }
}
The brand is resolved from the API key. The emailId is a path parameter — neither brandId nor emailId may appear in the body.

5. List Verified Domains

const { domains } = await brew.domains.list()

if (domains.length === 0) {
  throw new Error('You need a verified sending domain before sending.')
}

6. Start A Send

brew.sends.create(...) is campaign-only. Every send targets exactly one brand-owned audienceId (managed in the dashboard). For per-recipient event-driven delivery — welcome flows, drip campaigns, transactional fires — chain brew.automationRuns.fire(...) against a published automation graph instead (see the cookbook). Audience send example:
const { audiences } = await brew.audiences.list()

if ('emailId' in generated) {
  const sendResult = await brew.sends.create({
    emailId: generated.emailId,
    domainId: domains[0]!.domainId,
    audienceId: audiences[0]!.audienceId,
    subject: 'Welcome to Brew',
  })

  console.log(sendResult.status)
  console.log(sendResult.runId)
}
Scheduled send example (pin to a specific email version with emailVersionId):
await brew.sends.create({
  emailId: 'email_123',
  emailVersionId: 'emv_123_v2',
  domainId: 'domain_123',
  audienceId: 'audience_123',
  subject: 'Launch update',
  scheduledAt: '2099-01-01T00:00:00.000Z',
})

Handle Errors

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

try {
  await brew.domains.list()
} catch (error) {
  if (error instanceof BrewApiError) {
    console.error(error.code, error.message, error.requestId)
  }
  throw error
}

Next Steps

Resource Surface

See the current TypeScript SDK resources and methods.

API Reference

See the raw HTTP contract behind the SDK.

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.