White Label Branding API and MCP Guide

This guide explains Configure per-domain White Label branding — brand, logos, and branded dashboard/webmail hosts — via REST or MCP. so you can complete the TrekMail task with confidence.

Article details

Type, difficulty, plans, and last updated info.

Type
Reference
Difficulty
Intermediate
Plans
Pro · Agency · + White Label add-on
Last updated
Jun 17, 2026

Per-domain White Label branding can be configured end to end through the API and MCP — no dashboard required. An agent can set a domain's brand name and colors, upload logos, turn on branded dashboard/webmail hosts, read back the CNAME records it needs to create, and trigger DNS verification. This is the same branding the dashboard's Branding tab writes; the API just lets an agent (Claude, OpenClaw, your own script) do it autonomously.

Branding is configured per domain (the domain is the numeric id). A domain can have its own brand (custom), inherit the account default (inherit), or be off. The branded URLs are subdomains of that domain — dashboard.yourbrand.com for the dashboard and mail.yourbrand.com for webmail — and they point a CNAME at wl.trekmail.net.

The add-on gate

Branding settings always save, on every plan, with or without the White Label add-on. What the add-on controls is whether the branded URLs go live:

  • With the White Label add-on active — enabled hosts move from draft to pending_dns, and once the CNAME resolves and SSL is issued, to active.
  • Without the add-on — branding still saves and reads back fine, but the hosts stay draft and never serve. You can configure everything ahead of time and the moment the add-on is active the hosts start provisioning.

POST /branding/verify-dns returns 422 white_label_inactive when the add-on isn't active, because there's nothing to verify yet.

Required scopes

Reads use domains:read. Every mutation — set branding, upload/remove a logo, verify DNS, create a preview, delete branding — uses domains:write. Branding reuses your existing domain scopes; there's no separate branding scope.

Scope Covers
domains:read GET /domains/{id}/branding
domains:write PATCH, logo PUT/DELETE, verify-dns, preview, branding DELETE

REST endpoints

All endpoints live under https://trekmail.net/api/v1. {id} is the numeric domain id.

Endpoint Method Scope What it does
/api/v1/domains/{id}/branding GET domains:read Read the full branding state — mode, add-on status, brand fields, hosts, the CNAME records to create, and the CNAME target
/api/v1/domains/{id}/branding PATCH domains:write Partial-merge update of the brand — mode, name, colors, host toggles/labels, sender/support, scope
/api/v1/domains/{id}/branding/logo/{slot} PUT domains:write Upload a logo (slot = light, dark, or favicon) from base64
/api/v1/domains/{id}/branding/logo/{slot} DELETE domains:write Remove a logo slot
/api/v1/domains/{id}/branding/verify-dns POST domains:write Queue DNS verification for the enabled branded hosts
/api/v1/domains/{id}/branding/preview POST domains:write Mint a short-lived preview URL of the branded experience
/api/v1/domains/{id}/branding?scope=domain|all DELETE domains:write Clear branding for this domain, or for the whole account

Every endpoint except verify-dns and preview returns the same branding payload that GET returns, so a single round trip tells you the new state.

The branding payload

{
  "data": {
    "mode": "custom",
    "white_label_addon_active": true,
    "brand": {
      "id": 42,
      "name": "Northwind Mail",
      "primary_color": "#2563eb",
      "accent_color": "#10b981",
      "logo_url": "https://trekmail.net/storage/branding/42/light.png",
      "logo_dark_url": "https://trekmail.net/storage/branding/42/dark.png",
      "favicon_url": "https://trekmail.net/storage/branding/42/favicon.png",
      "support_email": "support@northwind.com",
      "support_url": "https://help.northwind.com",
      "sender_email": "noreply@northwind.com"
    },
    "hosts": [
      { "kind": "dashboard", "hostname": "dashboard.northwind.com", "subdomain_label": "dashboard", "enabled": true, "status": "pending_dns" },
      { "kind": "webmail", "hostname": "mail.northwind.com", "subdomain_label": "mail", "enabled": true, "status": "pending_dns" }
    ],
    "dns_records": [
      { "type": "CNAME", "name": "dashboard.northwind.com", "value": "wl.trekmail.net", "proxied": false },
      { "type": "CNAME", "name": "mail.northwind.com", "value": "wl.trekmail.net", "proxied": false }
    ],
    "cname_target": "wl.trekmail.net"
  }
}

brand is null when mode is off. Host status is one of draft (add-on inactive), pending_dns (waiting on the CNAME / SSL), or active.

Read the current branding

curl -s "https://trekmail.net/api/v1/domains/123/branding" \
  -H "Authorization: Bearer tm_live_your_token"

Set the brand (partial merge)

PATCH is a partial merge — any field you omit is preserved. Send only what you're changing.

curl -s -X PATCH "https://trekmail.net/api/v1/domains/123/branding" \
  -H "Authorization: Bearer tm_live_your_token" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: brand-123-initial" \
  -d '{
    "mode": "custom",
    "name": "Northwind Mail",
    "primary_color": "#2563eb",
    "accent_color": "#10b981",
    "dashboard_enabled": true,
    "dashboard_label": "dashboard",
    "webmail_enabled": true,
    "webmail_label": "mail",
    "support_email": "support@northwind.com",
    "support_url": "https://help.northwind.com",
    "sender_email": "noreply@northwind.com"
  }'

The body fields:

Field Notes
mode off, inherit (use the account default), or custom (domain-specific brand). If branding is currently off, you must pass mode to re-enable it.
name Brand name shown in the sidebar, login screen, page titles, and email signatures.
primary_color / accent_color Hex codes (#2563eb).
dashboard_enabled / dashboard_label Toggle and subdomain label for the dashboard host.
webmail_enabled / webmail_label Toggle and subdomain label for the webmail host.
support_email Reply-To / support address on branded transactional emails.
support_url Help-center URL — adds a "Need help?" link to branded email footers.
sender_email Visible From on branded transactional emails. Must be on a domain with a verified DKIM key on the account — otherwise the update is rejected.
scope domain (this domain only — default), account_default (also make it the account default for new domains), or all (also push it to every existing domain).

Upload a logo

Logos go in as base64. slot is light, dark, or favicon. Accepted: PNG and JPG for any slot, plus ICO for favicon. Max 1 MB. SVG is rejected for security reasons.

curl -s -X PUT "https://trekmail.net/api/v1/domains/123/branding/logo/light" \
  -H "Authorization: Bearer tm_live_your_token" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: brand-123-logo-light" \
  -d "{\"content_base64\":\"$(base64 -w0 logo-light.png)\"}"

Remove a slot with DELETE:

curl -s -X DELETE "https://trekmail.net/api/v1/domains/123/branding/logo/dark" \
  -H "Authorization: Bearer tm_live_your_token"

Both return the branding payload with the updated logo_url / logo_dark_url / favicon_url.

Verify DNS

After you've created the CNAME records (see the flow below), queue verification:

curl -s -X POST "https://trekmail.net/api/v1/domains/123/branding/verify-dns" \
  -H "Authorization: Bearer tm_live_your_token" \
  -H "Idempotency-Key: brand-123-verify"
{ "data": { "status": "queued", "hosts": 2 } }

This is async — re-read GET /branding and watch host status move to active. Returns 422 white_label_inactive if the White Label add-on isn't active.

Create a live preview

POST /branding/preview mints a short-lived URL so you can see the branded experience before DNS is live:

curl -s -X POST "https://trekmail.net/api/v1/domains/123/branding/preview" \
  -H "Authorization: Bearer tm_live_your_token" \
  -H "Idempotency-Key: brand-123-preview"
{ "data": { "url": "https://trekmail.net/_preview/abc123", "expires_in": 3600 } }

Returns 422 preview_disabled when the preview feature is off for the account, or 422 no_brand when there's no brand to preview (mode is off, or nothing has been set yet).

Delete branding

curl -s -X DELETE "https://trekmail.net/api/v1/domains/123/branding?scope=domain" \
  -H "Authorization: Bearer tm_live_your_token"

scope=domain clears just this domain; scope=all clears branding across the account. Returns the branding payload.

MCP tools

Seven tools cover the surface. The read tool needs only domains:read. Every mutating tool requires TREKMAIL_ALLOW_DESTRUCTIVE=true — the same gate as set_domain_smtp and update_domain_signature.

Tool Description
get_domain_branding Read the full branding state for a domain — mode, add-on status, brand fields, hosts, and the dns_records to create
set_domain_branding Set the brand (partial merge): mode, name, colors, dashboard/webmail toggles and labels, support/sender, scope (gated: requires TREKMAIL_ALLOW_DESTRUCTIVE)
set_domain_brand_logo Upload a logo from base64 to the light, dark, or favicon slot (gated: requires TREKMAIL_ALLOW_DESTRUCTIVE)
verify_domain_branding_dns Queue DNS verification for the enabled branded hosts (gated: requires TREKMAIL_ALLOW_DESTRUCTIVE)
create_branding_preview Mint a short-lived preview URL of the branded experience (gated: requires TREKMAIL_ALLOW_DESTRUCTIVE)
remove_domain_brand_logo Remove a logo slot (gated: requires TREKMAIL_ALLOW_DESTRUCTIVE)
remove_domain_branding Clear branding for the domain or the whole account (gated: requires TREKMAIL_ALLOW_DESTRUCTIVE)

get_domain_branding is read-only and always available; the other six are disabled unless TREKMAIL_ALLOW_DESTRUCTIVE=true.

The autonomous end-to-end flow

If your domain's DNS is on Cloudflare, an agent can take a domain from no branding to a live branded host with zero human steps, because the existing Cloudflare DNS tools (apply_cloudflare_dns) can write the CNAMEs that get_domain_branding hands back.

  1. Set the brand. set_domain_branding(mode=custom, name, primary_color, accent_color, dashboard_enabled=true, webmail_enabled=true).
  2. Upload logos (optional). set_domain_brand_logo(slot="light", content_base64=…), repeat for dark and favicon.
  3. Read the DNS records. get_domain_branding → read the dns_records array (two CNAME records pointing at wl.trekmail.net, proxied:false).
  4. Write the CNAMEs. apply_cloudflare_dns with those records — proxy off (orange cloud breaks SSL provisioning).
  5. Verify. verify_domain_branding_dns.
  6. Poll. Re-call get_domain_branding until each host's status is active.
  7. Preview (optional). create_branding_preview for a live demo URL before you point customers at the branded domain.

Worked example (MCP)

set_domain_branding(
  domain_id=123,
  mode="custom",
  name="Northwind Mail",
  primary_color="#2563eb",
  accent_color="#10b981",
  dashboard_enabled=true,
  webmail_enabled=true,
  support_email="support@northwind.com",
  sender_email="noreply@northwind.com"
)

set_domain_brand_logo(domain_id=123, slot="light", content_base64="iVBORw0KGgo…")
set_domain_brand_logo(domain_id=123, slot="dark", content_base64="iVBORw0KGgo…")

get_domain_branding(domain_id=123)
# → dns_records: [
#     { type: "CNAME", name: "dashboard.northwind.com", value: "wl.trekmail.net", proxied: false },
#     { type: "CNAME", name: "mail.northwind.com",      value: "wl.trekmail.net", proxied: false }
#   ]

apply_cloudflare_dns(domain_ids=[123])   # writes the CNAMEs, proxy off

verify_domain_branding_dns(domain_id=123)   # → { status: "queued", hosts: 2 }

# poll until active
get_domain_branding(domain_id=123)
# → hosts[].status: "active"

create_branding_preview(domain_id=123)   # → { url, expires_in } — optional live demo

Ask the agent to report back the branded hostnames and the final host statuses so you know it actually went live, not just pending_dns.

Gotchas

  • The add-on gates going live, not saving. Branding saves on any plan; hosts only leave draft once the White Label add-on is active. verify-dns returns 422 white_label_inactive until it is. Set everything up early — it'll start serving the moment the add-on turns on.
  • PATCH is a partial merge. Omitted fields are preserved. To change only the accent color, send {"accent_color":"#10b981"} — you don't have to resend name, logos, or toggles.
  • Re-enabling from off requires mode. If branding is currently off, a PATCH that omits mode won't turn it back on. Pass mode=custom (or inherit) to re-enable.
  • sender_email needs a verified DKIM domain. The From address you set must be on a domain that already has a DKIM key provisioned on the account, or the update is rejected. Verify the domain's DKIM (retry_domain_dkim / get_dns_check) before setting a custom sender.
  • Logos are base64, ≤1 MB, no SVG. Send PNG or JPG (ICO also allowed for favicon) as content_base64. SVG is rejected. Compress large source files first.
  • Keep the CNAME unproxied. The dns_records come back with proxied:false for a reason — a Cloudflare orange cloud (or any CDN proxy) breaks the Let's Encrypt SSL provisioning. Apply them grey-cloud.
  • Mutations need the destructive gate. Every tool except get_domain_branding requires TREKMAIL_ALLOW_DESTRUCTIVE=true, matching set_domain_smtp / update_domain_signature.

Related articles

Jump to nearby guides that continue the workflow.

We use cookies for essential functionality. No ads, no ad tracking.

Sign in to TrekMail

Access your dashboard, mailboxes and DNS.

or
or

Reset email sent

If an account exists for this email, we've sent password reset instructions.

By continuing, you agree to TrekMail's Terms and Privacy Policy.