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.

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_dns to active after their CNAME resolves and SSL is issued.
  • Cancellation grace: the account owner keeps read-only access until the displayed hard_delete_at time. 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.

  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 → copy the returned dns_records array. Do not guess or generate values.
  4. Write the CNAMEs. Publish those records with the proxy off. In Cloudflare, this means a grey cloud so DNS and SSL validation can work.
  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)
# → 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.
  • PATCH is 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 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 returned CNAME records unproxied. A Cloudflare orange cloud, or another CDN proxy, prevents DNS and SSL validation. Publish the dns_records as returned, with proxied:false.
  • Write actions need the right access. Every tool except get_domain_branding changes 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.

We use necessary technologies to operate and secure TrekMail. Selecting Okay also allows limited analytics and advertising measurement described in our Cookie Policy.

Sign in to TrekMail

Access your dashboard, mailboxes and DNS.

or

12 characters passwords match

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.