Author SHA1 Message Date
jknapp 0366e864f8 Merge pull request 'Add customer article on releasing/allowing quarantined email' (#14) from docs/release-quarantined-email into main
Build and deploy / deploy (push) Successful in 59s
Reviewed-on: #14
2026-09-09 17:40:47 +00:00
7 changed files with 26 additions and 141 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

@@ -8,46 +8,50 @@ sidebar:
import { Steps, Aside } from '@astrojs/starlight/components';
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. You don't have to wait for the daily notice to do this — see below.
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.
<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
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>
1. Go to your mail filtering portal at `https://pmg01.cloud-hosting.io:8006/quarantine`. Choose **Request Quarantine Link**.
1. Go to your mail filtering portal at `https://pmg01.cloud-hosting.io:8006/quarantine`.
![The mail filtering portal's login screen, with Request Quarantine Link next to Login](~/assets/screenshots/email/email-quarantine-login.png)
2. Enter the email address you want to check, and choose to receive a sign-in link.
2. Enter the email address you want to check.
3. Open the email that arrives and follow the link — it signs you in without a separate password.
![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)
4. Review what's listed. For anything you want, choose **Deliver** to send it to your inbox as normal.
</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
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.
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.
<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.
</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
**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,7 +63,6 @@ npx tsx tools/screenshots/capture-dns.ts
| `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-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).
-118
View File
@@ -1,118 +0,0 @@
/**
* 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); });