White Label Branding API and MCP Guide
Configure per-domain White Label branding (brand, logos, and branded dashboard/webmail hosts) via REST or MCP.
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
- Sep 10, 2026
Per-domain White Label branding can be configured end to end through the API and MCP, with no dashboard required. An agent can set a domain's brand name and colors, upload logos, turn on branded dashboard and webmail hosts, read the DNS records it needs to create, and request DNS verification. This is the same branding the dashboard's Branding tab writes; the API simply lets an agent or script do it for you.
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 API returns the branded hostnames and the CNAME records for that domain. Always copy the returned records exactly. Do not build a hostname or CNAME target from an example in this guide.
The add-on gate
Every mail plan includes a 30-day White Label trial and preview. Use that time to configure the brand and test the experience before you make branded hosts available to customers.
The API follows the same entitlement as the White Label dashboard:
- Active trial or paid add-on: read and write scopes are available. Enabled hosts move from
pending_dnstoactiveafter their CNAME resolves and SSL is issued. - Cancellation grace: the account owner keeps read-only access until the displayed
hard_delete_attime. Writes are blocked, and delegated connections lose White Label access immediately. - No active entitlement: White Label scopes are removed from the credential's effective permissions and its MCP tools are not loaded.
If a stored token once had a White Label scope but the entitlement is no longer active, the API returns 403 scope_blocked_by_entitlement with a direct next step. Creating a broader token does not bypass the entitlement.
Required scopes
Branding has its own scopes. This keeps automation that manages ordinary domains from seeing or changing the reseller identity by accident.
| Scope | Covers |
|---|---|
branding:read |
Read a domain's brand, assets, branded hosts, mail-zone status, and remaining DNS actions |
branding:write |
Change branding, upload or remove assets, request a preview, verify DNS, or clear branding |
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 | branding:read |
Read the full branding state: mode, add-on status, brand fields, mail-zone state, hosts, the CNAME records to create, and the CNAME target |
/api/v1/domains/{id}/branding |
PATCH | branding:write |
Partial-merge update of the brand: mode, name, colors, host and mail-zone toggles, sender/support, scope |
/api/v1/domains/{id}/branding/logo/{slot} |
PUT | branding:write |
Upload a logo (slot = light, dark, or favicon) from base64 |
/api/v1/domains/{id}/branding/logo/{slot} |
DELETE | branding:write |
Remove a logo slot |
/api/v1/domains/{id}/branding/verify-dns |
POST | branding:write |
Queue DNS verification for the enabled branded hosts |
/api/v1/domains/{id}/branding/preview |
POST | branding:write |
Create a 72-hour preview URL of the branded experience |
/api/v1/domains/{id}/branding?scope=domain|all |
DELETE | branding: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"
},
"mail_zone": {
"enabled": true,
"domain": "northwind.com",
"dns_status": "pending_dns",
"client_hosts_status": "pending_dns",
"records": [
{ "type": "TXT", "name": "spf.northwind.com", "value": "v=spf1 include:spf.trekmail.net -all" },
{ "type": "CNAME", "name": "imap.northwind.com", "value": "imap.trekmail.net" },
{ "type": "CNAME", "name": "dav.northwind.com", "value": "trekmail.net" }
],
"dav_url": "https://trekmail.net/dav/files/account/",
"dav_ready": false,
"cert_expires_at": null,
"checked_at": "2026-08-29T06:20:11+00:00"
},
"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": "<returned dashboard host>", "value": "<returned CNAME target>", "proxied": false },
{ "type": "CNAME", "name": "<returned webmail host>", "value": "<returned CNAME target>", "proxied": false }
],
"cname_target": "<returned CNAME target>"
}
}
brand is null when mode is off. mail_zone is always present; mail_zone.enabled tells you whether the setting was requested, and its two status fields distinguish pending, active, failed, and cleanup states. Host status reports whether DNS and SSL are still pending or the host is active. The placeholder values in the example are deliberate: publish the exact values returned in dns_records and mail_zone.records, never a guessed platform hostname.
mail_zone describes the brand's own mail hostnames (see below). dns_status covers the mail DNS state and client_hosts_status covers client-host and certificate state; both read off, pending_dns, active, or failed. records contains only DNS records that still need attention after the latest successful check. dav_url is always safe to use: it stays on TrekMail until the branded DAV certificate and restricted web route are ready. Switch only when dav_ready becomes true; cert_expires_at then reports the earliest certificate expiry for the branded mail-app hosts.
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, so send only what you are 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",
"mail_zone_enabled": true,
"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. |
mail_zone_enabled |
Serve mail apps and DAV sync under the brand's own domain, so customers see names such as imap.northwind.com and dav.northwind.com instead of ours. The zone belongs to the brand, not to the single domain, so it needs mode=custom or scope=account_default; sending it against an inherit domain returns 422 inherited_brand. Read mail_zone.dns_status, mail_zone.client_hosts_status, mail_zone.dav_ready, and mail_zone.records to track provisioning and publish the remaining records. |
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. It must be on a domain with a verified DKIM key on the account, or 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. The default scope=domain only changes a domain in custom mode; it never follows an inherited profile. To intentionally change the shared profile through an inherit domain, pass scope=account_default and use an unconstrained branding:write token. Domain-constrained tokens cannot mutate the account default.
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" \
-H "Idempotency-Key: brand-123-logo-dark-remove"
Both return the branding payload with the updated logo_url / logo_dark_url / favicon_url. PUT accepts scope in the JSON body; DELETE accepts it as a query parameter. An implicit domain-scoped mutation against an inherited profile returns 422 inherited_brand.
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 runs in the background. Re-read GET /branding and watch host status move to active. If White Label lapses, the request returns 403 scope_blocked_by_entitlement with a reactivation hint.
It also re-checks the brand's mail zone when there is one, so mail_zone.dns_status and mail_zone.client_hosts_status move on the same call. You do not have to call it at all for the zone: we re-check waiting zones on a schedule and switch them on within minutes of the records resolving. verify-dns just asks for that now instead of at the next sweep.
Mail on the brand's own domain
mail_zone_enabled puts the reseller's name in their customers' mail apps and DAV sync clients. Turn it on, then publish every record returned in mail_zone.records. On first setup this normally means an SPF TXT record plus IMAP and DAV CNAME records. The list shrinks after each successful check. A standard record such as SMTP or autoconfig appears here only if its current value is blocking White Label setup, so an empty list means there is no DNS change left for you to make. The exact names and targets in your response are authoritative.
Use a CNAME rather than an A record where the returned record calls for one, and leave Cloudflare's cloud grey. Mail and DAV clients must connect directly; a DNS proxy can break certificate checks and non-browser protocols. The response indicates every record you need to publish, so do not add guessed mail records.
Once the records resolve, TrekMail issues the certificates and activates the hostnames. Watch mail_zone.client_hosts_status for active and mail_zone.dav_ready for true. Keep using the returned dav_url; it changes from the platform address to the branded address only after DAV is safe to serve. If the host status reports failed, run DNS verification again and open a support ticket if it continues to fail.
Create a live preview
POST /branding/preview creates a 72-hour 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"
The response contains a preview URL that expires after 72 hours. It returns 422 no_brand when there is no brand to preview because branding is off or has not 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" \
-H "Idempotency-Key: brand-123-remove"
scope=domain clears just this domain; scope=all clears branding across the account. Returns the branding payload.
MCP tools
Seven tools cover branding inside the 20-tool white_label toolset. They are registered only when the connection has an effective branding scope and White Label is available. The read tool needs branding:read; the other six need branding:write. A locally hosted MCP server may also require its administrator to allow write actions.
| Tool | Description |
|---|---|
get_domain_branding |
Read the full branding state for a domain: mode, add-on status, brand fields, hosts, the dns_records to create, and mail_zone |
set_domain_branding |
Set the brand (partial merge): mode, name, colors, dashboard/webmail/mail-zone toggles and labels, support/sender, scope |
set_domain_brand_logo |
Upload a logo from base64 to the light, dark, or favicon slot |
verify_domain_branding_dns |
Queue DNS verification for the enabled branded hosts |
create_branding_preview |
Create a preview URL of the branded experience |
remove_domain_brand_logo |
Remove a logo slot |
remove_domain_branding |
Clear branding for the domain or the whole account |
get_domain_branding is read-only. During the owner's cancellation grace it remains available, while all six write tools disappear. Without White Label entitlement, none of these tools is advertised in tools/list.
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→ copy the returneddns_recordsarray. Do not guess or generate values. - Write the CNAMEs. Publish those records with the proxy off. In Cloudflare, this means a grey cloud so DNS and SSL validation can work.
- 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)
# → Copy the returned dns_records exactly. Do not substitute an example host or target.
apply_cloudflare_dns(domain_ids=[123]) # writes the CNAMEs, proxy off
verify_domain_branding_dns(domain_id=123)
# poll until active
get_domain_branding(domain_id=123)
# → hosts[].status: "active"
create_branding_preview(domain_id=123) # 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 entitlement gates the API and MCP surface. An active trial or paid add-on is required for writes. The owner gets a read-only recovery window after cancellation; everyone else loses these tools immediately.
PATCHis a partial merge. Omitted fields are preserved. To change only the accent color, send{"accent_color":"#10b981"}. You do not have to resend the name, 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 returned CNAME records unproxied. A Cloudflare orange cloud, or another CDN proxy, prevents DNS and SSL validation. Publish the
dns_recordsas returned, withproxied:false. - Write actions need the right access. Every tool except
get_domain_brandingchanges data, so use the required write scope and enable writes if your locally hosted MCP administrator has chosen to guard them.
Related articles
Jump to nearby guides that continue the workflow.