diff --git a/src/assets/screenshots/whp/whp-email-autodiscovery.png b/src/assets/screenshots/whp/whp-email-autodiscovery.png new file mode 100644 index 0000000..7f5ba77 Binary files /dev/null and b/src/assets/screenshots/whp/whp-email-autodiscovery.png differ diff --git a/src/content/docs/whp/how-to/create-an-email-account.mdx b/src/content/docs/whp/how-to/create-an-email-account.mdx index f4b7da4..a2f431e 100644 --- a/src/content/docs/whp/how-to/create-an-email-account.mdx +++ b/src/content/docs/whp/how-to/create-an-email-account.mdx @@ -34,9 +34,41 @@ import Support from '~/content/partials/support-link.mdx'; +## 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. + + + + + ## 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: diff --git a/src/content/docs/whp/how-to/manage-dns-records.mdx b/src/content/docs/whp/how-to/manage-dns-records.mdx index 65ed41c..e1780ae 100644 --- a/src/content/docs/whp/how-to/manage-dns-records.mdx +++ b/src/content/docs/whp/how-to/manage-dns-records.mdx @@ -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. +## 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 diff --git a/tools/screenshots/capture-email.ts b/tools/screenshots/capture-email.ts new file mode 100644 index 0000000..1496721 --- /dev/null +++ b/tools/screenshots/capture-email.ts @@ -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, '.cloud-hosting.io'], + [/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '.cloud-hosting.io'], + [/WHP\d+(-[A-Z0-9]+)?\b/g, ''], + [/whp\d+(-[a-z0-9]+)?\b/gi, ''], + [/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, ''], + ]; + 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
 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); });