docs(email): mail-client autodiscovery + external-DNS records #2

Merged
jknapp merged 1 commits from docs/mail-autodiscovery into main 2026-06-22 15:36:32 +00:00
4 changed files with 144 additions and 1 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

@@ -34,9 +34,41 @@ import Support from '~/content/partials/support-link.mdx';
</Steps> </Steps>
## Auto-configure your mail app
Most modern mail apps — Outlook, Apple Mail, Thunderbird, and the iOS and Android mail apps — can set themselves up from your domain's DNS. You enter your **full email address** and **password**, and the app finds the right servers, ports, and security settings on its own.
**If your domain uses our nameservers, this already works** — we add the necessary records automatically when you add the domain, so there's nothing for you to do.
### If your DNS is hosted elsewhere
If your domain's DNS lives at another provider (Cloudflare, GoDaddy, Namecheap, and so on), your mail app can't auto-configure until you add a few records there yourself. The Email page builds the exact records for you: open the **Mail Client Setup** section, pick the domain, and copy them in.
![The Mail Client Setup section on the WHP Email page, showing autodiscovery DNS records for a domain](~/assets/screenshots/whp/whp-email-autodiscovery.png)
Add these records to the domain's zone at your DNS provider. The names are **relative to your domain** — most providers fill in the rest automatically, so `autoconfig` becomes `autoconfig.example.com`.
| Type | Name | Priority | Weight | Port | Value |
| --- | --- | --- | --- | --- | --- |
| CNAME | `autoconfig` | — | — | — | your mail server |
| SRV | `_autodiscover._tcp` | 0 | 0 | 443 | your mail server |
| SRV | `_imaps._tcp` | 0 | 1 | 993 | your mail server |
| SRV | `_submission._tcp` | 0 | 1 | 587 | your mail server |
| SRV | `_pop3s._tcp` | 0 | 1 | 995 | your mail server |
Use the **mail server hostname shown in the Mail Client Setup section** as the value — it's the same host your **MX** record points at. The `_pop3s` record is only needed if you read mail over POP3 instead of IMAP. Click **Copy records** to grab them all at once in zone-file format.
<Aside type="tip">
If your provider has a proxy toggle (such as Cloudflare's orange cloud), keep these records **DNS only** — proxying them stops mail clients from reading them.
</Aside>
<Aside type="note">
These records only help apps *find* the server. You still create the mailbox in WHP first, and your domain's **MX** record must point at our mail server for mail to be delivered.
</Aside>
## Set up your email client ## Set up your email client
The exact IMAP, POP3, and SMTP hostnames are listed on the Email page click **Setup Instructions → View Instructions** under **Mail Server Access** for a step-by-step that includes the right hostnames, ports, and security settings for your server. Most apps configure themselves from the records above once you enter your address and password. If yours doesn't support that — or you'd rather enter the settings by hand — the exact IMAP, POP3, and SMTP hostnames are listed on the Email page: click **Setup Instructions → View Instructions** under **Mail Server Access** for a step-by-step that includes the right hostnames, ports, and security settings for your server.
The typical settings look like this; substitute the hostname shown in the Setup Instructions: The typical settings look like this; substitute the hostname shown in the Setup Instructions:
@@ -48,6 +48,12 @@ Each domain starts with a standard zone created automatically when the domain wa
Editing **MX**, **NS**, or the SPF/DKIM **TXT** records can break email delivery or hand DNS control away from us. Only change these if you know exactly what you're doing. Editing **MX**, **NS**, or the SPF/DKIM **TXT** records can break email delivery or hand DNS control away from us. Only change these if you know exactly what you're doing.
</Aside> </Aside>
## Mail autodiscovery records
When we host your DNS, the records that let mail apps configure themselves — an `autoconfig` CNAME plus a set of `_autodiscover` / `_imaps` / `_submission` / `_pop3s` **SRV** records — are already in your zone. You don't need to add them.
If your DNS is at another provider, add them there by hand. WHP builds the exact records for each domain on the **Email** page — see [Auto-configure your mail app](/whp/how-to/create-an-email-account/#auto-configure-your-mail-app).
## Add a record ## Add a record
<Steps> <Steps>
+105
View File
@@ -0,0 +1,105 @@
/**
* Email capture — the customer "Mail Client Setup" section on the Email page.
*
* Captures, as the demo customer:
* - whp-email-autodiscovery.png the Mail Client Setup accordion section,
* expanded, with the per-domain autodiscovery
* DNS records table + copyable zone block.
*
* Viewport-only (1440x900), redacted for our multi-server fleet: the mail-server
* hostname becomes a neutral placeholder, while the brand demo domain
* (whp-demo.anhh.co) is kept visible on purpose.
*
* Read-only: expands an accordion section for the screenshot, never saves.
*/
import { chromium, type Page } from 'playwright';
import { mkdir } from 'node:fs/promises';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const OUT_DIR = resolve(__dirname, '../../src/assets/screenshots/whp');
function need(name: string): string {
const v = process.env[name];
if (!v) throw new Error(`missing env: ${name}`);
return v;
}
const BASE = need('WHP_BASE');
const USER = need('WHP_USER');
const PASS = need('WHP_PASS');
const HIDE_CSS = `.navbar-text, .brand-full { visibility: hidden !important; }`;
async function login(page: Page) {
await page.goto(`${BASE}/login.php`, { waitUntil: 'domcontentloaded' });
await page.fill('input[name="user"]', USER);
await page.fill('input[name="password"]', PASS);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
/**
* Neutralise fleet-identifying text before the screenshot. The brand demo
* domain (anhh.co) is intentionally preserved; the mail-server host and any
* server hostnames/IPs are swapped for placeholders.
*/
async function redact(page: Page) {
await page.addStyleTag({ content: HIDE_CSS });
await page.evaluate(() => {
const swaps: [RegExp, string][] = [
[/mail\d+\.cloud-hosting\.io/gi, '<mail-server>.cloud-hosting.io'],
[/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '<your-server>.cloud-hosting.io'],
[/WHP\d+(-[A-Z0-9]+)?\b/g, '<YOUR-SERVER>'],
[/whp\d+(-[a-z0-9]+)?\b/gi, '<your-server>'],
[/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '<server-IP>'],
];
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
const nodes: Text[] = [];
let n: Node | null = walker.nextNode();
while (n) { nodes.push(n as Text); n = walker.nextNode(); }
for (const node of nodes) {
let v = node.nodeValue ?? '';
for (const [re, rep] of swaps) v = v.replace(re, rep);
if (v !== node.nodeValue) node.nodeValue = v;
}
});
}
async function main() {
await mkdir(OUT_DIR, { recursive: true });
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({
ignoreHTTPSErrors: true,
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 2,
});
const page = await ctx.newPage();
try {
await login(page);
await page.goto(`${BASE}/index.php?page=email-management`, { waitUntil: 'networkidle' });
// Expand the (collapsed-by-default) "Mail Client Setup" accordion section.
await page.locator('button[data-bs-target="#mail-client-setup"]').click();
await page.waitForSelector('#custMailDnsDomain', { state: 'visible' });
// The zone <pre> is populated on DOMContentLoaded; re-run to be safe.
await page.evaluate(() => {
const fn = (window as unknown as { renderCustMailDns?: () => void }).renderCustMailDns;
if (typeof fn === 'function') fn();
});
await page.waitForTimeout(500);
await redact(page);
const item = page.locator('#mail-client-setup').locator('xpath=ancestor::div[contains(@class,"accordion-item")]');
await item.scrollIntoViewIfNeeded();
await page.waitForTimeout(300);
const path = resolve(OUT_DIR, 'whp-email-autodiscovery.png');
await item.screenshot({ path });
console.log(`captured whp-email-autodiscovery -> ${path}`);
} finally {
await browser.close();
}
}
main().catch((err) => { console.error(err); process.exit(1); });