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.
▼
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
drafttopending_dns, and once the CNAME resolves and SSL is issued, toactive. - Without the add-on — branding still saves and reads back fine, but the hosts stay
draftand 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.
- Set the brand.
set_domain_branding(mode=custom, name, primary_color, accent_color, dashboard_enabled=true, webmail_enabled=true). - Upload logos (optional).
set_domain_brand_logo(slot="light", content_base64=…), repeat fordarkandfavicon. - Read the DNS records.
get_domain_branding→ read thedns_recordsarray (twoCNAMErecords pointing atwl.trekmail.net,proxied:false). - Write the CNAMEs.
apply_cloudflare_dnswith those records — proxy off (orange cloud breaks SSL provisioning). - Verify.
verify_domain_branding_dns. - Poll. Re-call
get_domain_brandinguntil each host'sstatusisactive. - Preview (optional).
create_branding_previewfor 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
draftonce the White Label add-on is active.verify-dnsreturns422 white_label_inactiveuntil it is. Set everything up early — it'll start serving the moment the add-on turns on. PATCHis a partial merge. Omitted fields are preserved. To change only the accent color, send{"accent_color":"#10b981"}— you don't have to resendname, logos, or toggles.- Re-enabling from off requires
mode. If branding is currentlyoff, aPATCHthat omitsmodewon't turn it back on. Passmode=custom(orinherit) to re-enable. sender_emailneeds 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) ascontent_base64. SVG is rejected. Compress large source files first. - Keep the CNAME unproxied. The
dns_recordscome back withproxied:falsefor 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_brandingrequiresTREKMAIL_ALLOW_DESTRUCTIVE=true, matchingset_domain_smtp/update_domain_signature.
Related articles
Jump to nearby guides that continue the workflow.