Add screenshots to the quarantine self-service article

Four screenshots of the actual PMG quarantine portal (login with
Request Quarantine Link, the request dialog, the held-mail list, and
the message-selected action toolbar), captured via a new
capture-quarantine.ts following the existing capture-*.ts pattern
(1440x900, deviceScaleFactor 2, viewport-only).

Redacted: real server hostname, the test mailbox address, and the
synthetic test-message content are all swapped for believable generic
customer-facing examples before each shot.

Restructured the article's two near-duplicate step lists ("Check your
quarantine" / "Get in without waiting") into one flow now that the
screenshots show it's genuinely a single screen, not two paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VXgBxaVABantBF8twz4LpU
This commit is contained in:
Claude
2026-09-09 11:41:33 -07:00
parent 9f30b6a2b3
commit 2044d9e0bc
7 changed files with 141 additions and 26 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -8,50 +8,46 @@ sidebar:
import { Steps, Aside } from '@astrojs/starlight/components'; import { Steps, Aside } from '@astrojs/starlight/components';
import Support from '~/content/partials/support-link.mdx'; import Support from '~/content/partials/support-link.mdx';
Mail our filters flag as likely spam isn't delivered to your inbox — it's held in **quarantine** instead, where you can review it, release anything that's actually wanted, and always allow a sender in future. Mail our filters flag as likely spam isn't delivered to your inbox — it's held in **quarantine** instead, where you can review it, release anything that's actually wanted, and always allow a sender in future. You don't have to wait for the daily notice to do this — see below.
<Aside type="tip">
**Need something released right now?** You don't have to wait for the daily notice — see [Get in without waiting](#get-in-without-waiting) below.
</Aside>
## Check your quarantine ## Check your quarantine
You'll normally get a **daily summary email** listing anything held for you — but the steps below work any time, not just after that email arrives. There's no separate password to remember; a link emailed to your own address is what signs you in.
<Steps> <Steps>
1. Go to your mail filtering portal at `https://pmg01.cloud-hosting.io:8006/quarantine`. 1. Go to your mail filtering portal at `https://pmg01.cloud-hosting.io:8006/quarantine`. Choose **Request Quarantine Link**.
2. Enter the email address you want to check, and choose to receive a sign-in link. ![The mail filtering portal's login screen, with Request Quarantine Link next to Login](~/assets/screenshots/email/email-quarantine-login.png)
3. Open the email that arrives and follow the link — it signs you in without a separate password. 2. Enter the email address you want to check.
4. Review what's listed. For anything you want, choose **Deliver** to send it to your inbox as normal. ![The Request Quarantine Link dialog with an email address entered](~/assets/screenshots/email/email-quarantine-request.png)
3. Open the email that arrives — usually within moments — and follow the link inside it.
4. Review what's listed for your address.
![The Spam Quarantine list showing a held message with its sender, subject, and spam score](~/assets/screenshots/email/email-quarantine-list.png)
5. Select a message to see its content and the Whitelist / Blacklist / Deliver / Delete options, then choose **Deliver** to send it to your inbox as normal.
![A held message selected, showing its content and the Whitelist, Blacklist, Deliver, and Delete buttons](~/assets/screenshots/email/email-quarantine-actions.png)
</Steps> </Steps>
<Aside type="tip">
**Need something released right now?** This works any time of day, not just once daily — request a new link as often as you need one. No need to wait for the daily summary email.
</Aside>
## Always allow a sender ## Always allow a sender
If mail from someone keeps getting caught and you know it's legitimate, allow their address (or their whole domain) from the same screen — choose **Whitelist** on a held message, or add the address directly from the quarantine portal. Future mail from that sender skips spam filtering for **your address specifically** — it doesn't change anything for the rest of your organization. If mail from someone keeps getting caught and you know it's legitimate, allow their address (or their whole domain) from the same screen — choose **Whitelist** on a held message (shown in the screenshot above), or add the address directly from the quarantine portal. Future mail from that sender skips spam filtering for **your address specifically** — it doesn't change anything for the rest of your organization.
<Aside type="caution"> <Aside type="caution">
Only allow senders you actually recognize. Allowing an address skips spam checks for it entirely, so it's worth being deliberate rather than allowing everything that shows up. Only allow senders you actually recognize. Allowing an address skips spam checks for it entirely, so it's worth being deliberate rather than allowing everything that shows up.
</Aside> </Aside>
## Get in without waiting
You'll normally get a **daily summary email** listing anything held for you, with links to review and release it from there. But if something time-sensitive is stuck in quarantine, you don't need to wait for that email:
<Steps>
1. Go to `https://pmg01.cloud-hosting.io:8006/quarantine` any time.
2. Enter your email address and request a sign-in link — a fresh one arrives within moments.
3. Follow the link and release the message immediately.
</Steps>
This works any time of day and isn't limited to once daily — request a new link as often as you need one.
## Common problems ## Common problems
**The sign-in link email hasn't arrived.** Check your spam folder — slightly ironic for a spam-filtering tool, but it happens. If it's still missing after a few minutes, contact us. **The sign-in link email hasn't arrived.** Check your spam folder — slightly ironic for a spam-filtering tool, but it happens. If it's still missing after a few minutes, contact us.
+1
View File
@@ -63,6 +63,7 @@ npx tsx tools/screenshots/capture-dns.ts
| `capture-site-builder.ts` | Site Builder editor states | `WHP_USER` | | `capture-site-builder.ts` | Site Builder editor states | `WHP_USER` |
| `capture-dns.ts` | Domains & DNS list, Add Domain modal, records editor, bulk toolbar | `WHP_USER` | | `capture-dns.ts` | Domains & DNS list, Add Domain modal, records editor, bulk toolbar | `WHP_USER` |
| `capture-email.ts` | Email page "Mail Client Setup" section (autodiscovery DNS records) | `WHP_USER` | | `capture-email.ts` | Email page "Mail Client Setup" section (autodiscovery DNS records) | `WHP_USER` |
| `capture-quarantine.ts` | PMG spam quarantine portal (login, request-link dialog, held-mail list, message actions) — **not a WHP page**, targets the mail filtering gateway directly | `PMG_BASE` + `PMG_TICKET_URL` (a real quarantine ticket link, e.g. from a test mailbox's "Request Quarantine Link" email) instead of WHP_USER/PASS |
Each script carries its own `redact()` (text-node + input-value swaps) so fleet hostnames, IPs, and customer data become neutral placeholders while brand/demo domains stay visible. Copy the closest existing script when adding a new section — match its viewport (1440×900), `deviceScaleFactor: 2`, and **read-only** discipline (open modals and tick boxes for the shot, but never save/delete/submit). Each script carries its own `redact()` (text-node + input-value swaps) so fleet hostnames, IPs, and customer data become neutral placeholders while brand/demo domains stay visible. Copy the closest existing script when adding a new section — match its viewport (1440×900), `deviceScaleFactor: 2`, and **read-only** discipline (open modals and tick boxes for the shot, but never save/delete/submit).
+118
View File
@@ -0,0 +1,118 @@
/**
* PMG quarantine capture — the customer self-service spam quarantine portal.
*
* Captures:
* - email-quarantine-login.png Login screen showing the "Request Quarantine
* Link" button next to Login.
* - email-quarantine-request.png The "Request Quarantine Link" dialog with an
* email address entered.
* - email-quarantine-list.png The authenticated quarantine grid showing held
* mail, redacted to generic example content.
* - email-quarantine-actions.png A selected message with the Whitelist /
* Blacklist / Deliver / Delete toolbar visible.
*
* Viewport-only (1440x900, deviceScaleFactor 2), redacted: the real server
* hostname, the real test mailbox address, and the real (internal-test-looking)
* sender/subject/body text are all swapped for generic customer-facing examples.
*
* Read-only: never actually delivers/whitelists/blacklists/deletes anything.
*/
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/email');
const BASE = process.env.PMG_BASE || 'https://pmg01.cloud-hosting.io:8006';
const TICKET_URL = process.env.PMG_TICKET_URL; // full quarantine?ticket=... URL
/** Swap real fleet/test identifiers for believable generic customer-facing text. */
async function redact(page: Page) {
await page.evaluate(() => {
const swaps: [RegExp, string][] = [
[/pmg01\.cloud-hosting\.io/gi, 'mail.yourdomain.com'],
[/claude@darksideofperfection\.com/gi, 'you@yourdomain.com'],
[/pmg-pilot-tester@anthropic\.com/gi, 'prizes@totally-legit-sweepstakes.example'],
[/SPAM: PMG pilot - spam detection test \(GTUBE\) \(retry\)/g, "SPAM: You've WON a prize!!!"],
[/SPAM: PMG pilot - clean delivery test \(retry\)/g, 'SPAM: Limited time offer - act now'],
[/This is a clean end-to-end delivery test, retried after greylist window\./g,
'Congratulations! You have been selected to receive an exclusive reward. Click below to claim yours before this offer expires.'],
[/GTUBE retry test\./g, "You've been selected for an exclusive reward!"],
[/XJS\*C4JDBQADN1\.NSBN3\*2IDNEN\*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL\*C\.34X/g,
'Click here to claim your prize before this limited-time offer expires: http://totally-legit-sweepstakes.example/claim'],
[/\b1010\b/g, '8.4'],
];
const docs: Document[] = [document];
for (const f of Array.from(document.querySelectorAll('iframe'))) {
try {
const doc = (f as HTMLIFrameElement).contentDocument;
if (doc) docs.push(doc);
} catch { /* cross-origin, skip */ }
}
for (const root of docs) {
const walker = root.createTreeWalker(root.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() {
if (!TICKET_URL) throw new Error('missing env: PMG_TICKET_URL');
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 {
// --- Shot 1: login screen, showing the Request Quarantine Link button ---
await page.goto(`${BASE}/quarantine`, { waitUntil: 'networkidle' });
await page.waitForSelector('text=Request Quarantine Link', { state: 'visible' });
await page.waitForTimeout(300);
await redact(page);
let p = resolve(OUT_DIR, 'email-quarantine-login.png');
await page.locator('.x-window').first().screenshot({ path: p });
console.log(`captured email-quarantine-login -> ${p}`);
// --- Shot 2: the Request Quarantine Link dialog, filled in ---
await page.getByRole('button', { name: 'Request Quarantine Link' }).click();
await page.getByRole('textbox', { name: 'Your E-Mail:' }).fill('you@yourdomain.com');
await page.waitForTimeout(200);
p = resolve(OUT_DIR, 'email-quarantine-request.png');
await page.locator('.x-window').last().screenshot({ path: p });
console.log(`captured email-quarantine-request -> ${p}`);
// --- Shot 3 & 4: authenticated quarantine view, via the real ticket link ---
await page.goto(TICKET_URL, { waitUntil: 'networkidle' });
await page.waitForSelector('grid[aria-label="Spam Quarantine"], .x-grid', { state: 'visible' }).catch(() => {});
await page.waitForTimeout(500);
await redact(page);
p = resolve(OUT_DIR, 'email-quarantine-list.png');
await page.screenshot({ path: p });
console.log(`captured email-quarantine-list -> ${p}`);
// Select the first row to reveal the action toolbar + message preview.
const firstRow = page.locator('.x-grid-row').first();
await firstRow.click();
await page.waitForTimeout(500);
await redact(page);
p = resolve(OUT_DIR, 'email-quarantine-actions.png');
await page.screenshot({ path: p });
console.log(`captured email-quarantine-actions -> ${p}`);
} finally {
await browser.close();
}
}
main().catch((err) => { console.error(err); process.exit(1); });