Skip to main content
Add an email editor to your existing admin app. Keep your app’s sign-in and customer records. Use one server-side Brew organization key to extract a customer’s brand, generate one email, authorize its canvas, and read the saved HTML. Groups and design-context reads are optional. The canvas is a Brew-hosted iframe. Customers need no Brew login. Your app controls navigation, prompts, and actions around it. The iframe has no SDK options for a custom theme, logo, or domain.

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:
Use SDK 13.0.0 or newer for the bounded brand waiter and session-provider 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:
Keep your own customer-to-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:
Persist 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:
The helper checks immediately, then every five seconds while the status is 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:
Creation needs an available brand slot and a non-empty credit balance. A domain already in your organization returns 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 saved brandId 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:
Save the email ID and its customer ownership before returning the ID to your app. No group or design-context request is required. The email uses the saved brand automatically. To make a prompt edit, authorize the saved email and call:
REST and SDK generation and editing wait for completion. The SDK’s default request timeout is four minutes. Use your existing request or worker model and configure the host’s budget accordingly. Both methods can return { 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, inspect customer.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 authenticated POST /api/brew/canvas-session endpoint. The SDK sends { target, sessionId? }. This endpoint must:
  1. Use your app’s existing login and CSRF validation.
  2. Resolve the caller’s customer, brand, target email, and permissions from server-owned records. Reject unauthorized targets.
  3. Create the first grant and store its owner, brand, target, and permissions.
  4. On renewal, verify the supplied session belongs to that caller and target, and recheck current access before calling canvas.sessions.refresh().
  5. Return the session directly as JSON with Cache-Control: no-store.
After those checks, create a grant for that email:
Use a stable, tenant-qualified 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’s headers option. The factory runs for every session create and renewal request. The SDK preserves JSON content type, same-origin credentials, and cancellation.
The React adapter uses the latest 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 browser flush() succeeds, authenticate and authorize the HTML request on your backend, resolve the customer’s saved brand and email IDs, and read:
The GET above reads the latest version. Manual edits can patch a displayed historical version, and 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

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.
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.
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.

Check Your Deployed Integration

If your admin app sets a Content Security Policy, allow https://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.

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.

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.