The Implementation Plan
Build these four pieces in order. Use your existing authentication, data store, framework, and deployment. Add background work only if your host’s request limit cannot accommodate generation. Start with one email per editing screen.
Copy the complete agent prompt to implement
all four tasks, or use the individual task prompts.
Prepare Your Backend
Install the SDK:headers option used below.
Create one organization-scoped API key with brands and emails permissions
at Settings → API. Store it in the server’s
BREW_API_KEY environment variable:
brandId and email-ownership records. Resolve them
from the signed-in customer on every backend request. A browser-supplied Brew
ID does not establish ownership. Private admin apps use server-authorized
canvas sessions, not public client IDs.
Authentication owns permissions,
Organization and Brand Credentials
owns brand selection, and Credits owns usage
charges.
Extract a Customer’s Brand
After authenticating the caller and authorizing customer setup, start extraction on your backend:created.brandId against the customer immediately. The response is a
flat object. It returns before extraction finishes. Reuse the same retry key
when recovering the same provisioning request. Use a new key for a new action.
Idempotency owns replay guarantees.
Wait for the saved brand on your backend when your request or existing worker
has enough time:
extracting. It stops at any other status; proceed only if ready is true.
Otherwise show the status and error, including completed with ready: false.
It accepts a cancellation signal. A BrewBrandExtractionTimeoutError ends
the wait without canceling extraction. Keep the saved brand ID and read its
status again instead of creating another brand.
Extraction can take a few minutes. For a short-lived host, expose
platform.brands.get({ brandId }) through your existing status endpoint and
let your app poll every five seconds with a deadline. Stop when the status
leaves extracting and require ready: true. No new job system is needed.
Once ready, pin a new client for this customer’s calls:
409 BRAND_DOMAIN_CONFLICT.
Reuse a saved brand only when your records authorize this customer to use it.
A matching domain does not authorize access to another customer’s brand.
There is no public method to restart extraction on an existing brand.
Create and Edit Emails
Resolve the savedbrandId from the authenticated customer’s records and use
platform.withBrand(brandId). This returns a new client without changing the
shared organization client. Generate one email:
{ response } without saving a design, so check the union before using IDs.
Prompt edits create a version. Metadata-only edits with title,
subjectLine, or groupId patch the saved design without a generation run.
Recover an Interrupted Request
PATCH edits have no replay protection and the SDK does not automatically retry them. An idempotency key does not change that. After an ambiguous timeout, inspectcustomer.emails.get(emailId, { include: 'versions' }) and
wait for any running generation to settle before deciding whether another
paid edit is needed.
409 EMAIL_RESULT_UNAVAILABLE means a design was saved but its result could
not be retrieved for the response. error.details includes committed: true,
emailId, emailVersionId, and runId. Recover with
customer.emails.get(emailId, { emailVersionId, include: 'html' }) using those
IDs. First persist the committed IDs in the authenticated customer’s ownership
records so the saved email can be authorized for recovery and editing. Do not
repeat the prompt. For POST generation, the original idempotency
key replays the same 409; read the saved version instead.
Authorize the Canvas in Your App
Add a same-origin authenticatedPOST /api/brew/canvas-session endpoint.
The SDK sends { target, sessionId? }. This endpoint must:
- Use your app’s existing login and CSRF validation.
- Resolve the caller’s customer, brand, target email, and permissions from server-owned records. Reject unauthorized targets.
- Create the first grant and store its owner, brand, target, and permissions.
- On renewal, verify the supplied session belongs to that caller and target,
and recheck current access before calling
canvas.sessions.refresh(). - Return the session directly as JSON with
Cache-Control: no-store.
externalId. Choose permissions on your
server. ['read'] gives read-only access; read is required in every grant.
Set parentOrigin from trusted deployment configuration. It must be the exact
HTTPS origin, including any port, without a path or trailing slash. Loopback
HTTP is accepted for development.
For renewal, call customer.canvas.sessions.refresh(sessionId) only after
checking the stored ownership record. A session ID alone proves nothing.
Refresh and revoke use the same issuing API principal. When access ends or
the customer signs out, call customer.canvas.sessions.revoke(sessionId).
Custom providers do not automatically revoke on unmount. Lost grants expire.
The response contains a single-use bootstrapToken. Do not cache, log, or put
it in a URL. The browser provider passes the uncached session to the iframe.
Understand the Access Boundary
explains why a public client ID is insufficient for this private workflow.
Mount the Canvas and Save Before Leaving
Pass your app’s current CSRF token through the provider’sheaders option.
The factory runs for every session create and renewal request. The SDK
preserves JSON content type, same-origin credentials, and cancellation.
getSession callback without reloading the
iframe when the CSRF token changes. Use your app’s CSRF header name and current
token. The alert keeps failures visible. If the
HTML request fails after flush() succeeds, the message confirms the design
is already saved; no generation or edit needs to run again.
Manual edits autosave. Await flush() before navigation, target changes,
unmounting, an API edit, or the HTML read. Use onDirtyChange for navigation
warnings. onSaved reports IDs and update time, not HTML. The SDK supports
React 18 and 19; createEmailCanvas() supports other frameworks.
Read the Saved HTML
After browserflush() succeeds, authenticate and authorize the HTML request
on your backend, resolve the customer’s saved brand and email IDs, and read:
flush() does not promote it to latest. For the exact
saved output, retain the canvas onSaved event’s emailId and non-null
emailVersionId, then pass that version to the authorized backend read.
Recheck email and version ownership. If no version ID was returned, resolve
that before promising an exact result instead of silently reading latest.
Wait for any active generation to finish before reading its final result.
Use the HTML in your sending system, or use customer.emails.export() for a
connected email platform. Neither action belongs in a canvas-save callback
without your backend’s authorization checks.
Optional Capabilities
Organize emails into groups
Organize emails into groups
Use
customer.emailGroups.create() and pass its groupId to generation.
Mount <EmailCanvas groupId={groupId} getSession={getSession} /> to show a
group. Authorize a group target in your session endpoint. Keep this optional
until your app needs several emails on one canvas.Inspect or customize the extracted design guidance
Inspect or customize the extracted design guidance
Use
customer.brand.get({ include: ['identity', 'emailDesign', 'imageStyle', 'logos'] }) to read the extracted context. Use customer.brand.patch() to
change saved guidance. Generation already uses that context automatically.Import designs or use the lower-level controller
Import designs or use the lower-level controller
customer.emails.import() accepts HTML, MJML, React Email JSX, or EML.
SDK Resources lists the available methods.
Embed the Email Canvas covers browser
controllers, events, collaboration, images, and draft recovery.Copy the Complete Agent Prompt
Paste this into the coding agent working in your admin app. Do not paste a key into the prompt. Give the agent access to your existing app’s source and test commands.Individual Task Prompts
Each prompt includes its own scope and completion checks. Run them in order.Task 1: Provision and map a customer brand
Task 1: Provision and map a customer brand
Task 2: Generate and edit one email
Task 2: Generate and edit one email
Task 4: Embed, flush, and retrieve HTML
Task 4: Embed, flush, and retrieve HTML
Check Your Deployed Integration
If your admin app sets a Content Security Policy, allowhttps://brew.new in
frame-src. Test your deployed HTTPS origin and a denied origin. Confirm the
CDN preserves the iframe framing policy. Check wrong-customer access,
read-only permissions, session renewal and revocation, manual save/reopen,
and image upload/crop against the real storage configuration.
Production checks are separate from mocked tests. Agree on any paid generation
before running it. Run the Open Source Demo
covers deployment checks that local fixtures cannot prove.
Related
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:- Self-Service Tools
- Talk to Our Team
Search Documentation
Type in the “Ask any question” search bar at the top left to instantly find relevant documentation pages.
Ask ChatGPT or Claude
Click here to open the Copy page menu at the top of this page, then send the page to ChatGPT, Claude, or your editor.