Files
selfpost/docs/assets/panel-ui/app.html
T
mix 9edb39a8ee
test / test (push) Waiting to run
docs: iterate panel UI mockups from review
Restore Status readings, inbound MX DNS and recipient modes, two-column Backup and Settings, domain-card help, and Host/name Type field height.

Co-Authored-By: Cursor <cursoragent@cursor.com>
2026-08-16 00:24:46 +03:00

1293 lines
65 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SelfPost — panel mockups</title>
<link rel="icon" href="../selfpost-icon-16.svg" type="image/svg+xml">
<link rel="stylesheet" href="mock.css">
</head>
<body>
<input class="ctrl" type="radio" name="role" id="role-global" checked>
<input class="ctrl" type="radio" name="role" id="role-domain">
<input class="ctrl" type="radio" name="vp" id="vp-desktop" checked>
<input class="ctrl" type="radio" name="vp" id="vp-phone">
<input class="ctrl" type="radio" name="theme" id="theme-light" checked>
<input class="ctrl" type="radio" name="theme" id="theme-dark">
<input class="ctrl" type="checkbox" id="feat-inbound" checked>
<input class="ctrl" type="checkbox" id="nav-open">
<input class="ctrl" type="radio" name="help" id="help-off" checked>
<input class="ctrl" type="radio" name="help" id="help-index">
<input class="ctrl" type="radio" name="help" id="help-status">
<input class="ctrl" type="radio" name="help" id="help-password">
<input class="ctrl" type="radio" name="help" id="help-dns">
<input class="ctrl" type="radio" name="help" id="help-records">
<input class="ctrl" type="radio" name="help" id="help-dmarc">
<input class="ctrl" type="radio" name="help" id="help-connection">
<input class="ctrl" type="radio" name="help" id="help-apps">
<input class="ctrl" type="radio" name="help" id="help-domain-settings">
<input class="ctrl" type="radio" name="help" id="help-export">
<header class="gallery">
<a class="brand-mini" href="index.html">Макеты</a>
<div class="seg">
<span>Роль</span>
<label for="role-global">Global</label>
<label for="role-domain">Domain-admin</label>
</div>
<div class="seg">
<span>Ширина</span>
<label for="vp-desktop">Desktop</label>
<label for="vp-phone">Phone</label>
</div>
<div class="seg">
<span>Тема</span>
<label for="theme-light">Light</label>
<label for="theme-dark">Dark</label>
</div>
<label class="g-only" for="feat-inbound">Inbound</label>
<a href="index.html">Оглавление</a>
</header>
<svg class="sprite" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<symbol id="i-status" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1.25 8.5h2.9L6.2 3.4l3.1 9.4 1.9-4.3h3.55"/></symbol>
<symbol id="i-domains" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6.25"/><path d="M1.9 8h12.2"/><path d="M8 1.75c1.85 1.8 2.8 4 2.8 6.25S9.85 12.45 8 14.25C6.15 12.45 5.2 10.25 5.2 8S6.15 3.55 8 1.75Z"/></symbol>
<symbol id="i-deliveries" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14.25 1.75 1.6 6.6l5 2.05 2.05 5z"/><path d="M14.25 1.75 6.6 8.65"/></symbol>
<symbol id="i-queue" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1.75 9.5h3.3l1 1.75h3.9l1-1.75h3.3v3.05a1.2 1.2 0 0 1-1.2 1.2H2.95a1.2 1.2 0 0 1-1.2-1.2z"/><path d="M1.75 9.5 3.4 3.2a1.25 1.25 0 0 1 1.2-.95h6.8a1.25 1.25 0 0 1 1.2.95l1.65 6.3"/></symbol>
<symbol id="i-log" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3.75 1.75h5.1l3.4 3.4v8.05a1.05 1.05 0 0 1-1.05 1.05H3.75a1.05 1.05 0 0 1-1.05-1.05V2.8a1.05 1.05 0 0 1 1.05-1.05Z"/><path d="M8.85 1.75v3.4h3.4"/><path d="M5.35 8.6h5.3M5.35 11.1h3.5"/></symbol>
<symbol id="i-inbound" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 9.5h11"/><path d="M8 2.75v6.2"/><path d="M5.4 6.4 8 9.05 10.6 6.4"/><path d="M3.2 12.6h9.6"/></symbol>
<symbol id="i-dmarc" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 1.85 2.75 3.7v4.2c0 3.15 2.15 5.2 5.25 6.25 3.1-1.05 5.25-3.1 5.25-6.25V3.7Z"/><path d="M5.4 8.05 7.15 9.8 10.7 6.2"/></symbol>
<symbol id="i-backup" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2.75" y="1.75" width="10.5" height="12.5" rx="1.15"/><path d="M2.75 8h10.5"/><path d="M6.4 4.85h3.2M6.4 11.15h3.2"/></symbol>
<symbol id="i-users" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.9 3.1a2.1 2.1 0 0 1 0 4.2"/><path d="M14.7 14.25a3.8 3.8 0 0 0-3.9-3.65"/><circle cx="5.5" cy="5.2" r="2.5"/><path d="M1.4 14.25a4.8 4.8 0 0 1 8.2 0"/></symbol>
<symbol id="i-help" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6.25"/><path d="M8 7.2V11.4"/><path d="M8 5.05v.01"/></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></symbol>
<symbol id="i-account" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="5.4" r="2.75"/><path d="M2.9 14.25a5.1 5.1 0 0 1 10.2 0"/></symbol>
<symbol id="i-out" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6.1 14.25H3.65a1.15 1.15 0 0 1-1.15-1.15V2.9a1.15 1.15 0 0 1 1.15-1.15H6.1"/><path d="M10.6 11.15 13.75 8 10.6 4.85"/><path d="M13.75 8H6.35"/></symbol>
<symbol id="i-menu" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2.5 4h11M2.5 8h11M2.5 12h11"/></symbol>
</svg>
<div class="app">
<nav class="nav">
<a class="brand" href="#status"><img src="../selfpost-stamp-compact.svg" width="220" height="100" alt="SelfPost"></a>
<div class="links">
<a class="n-status g-only" href="#status"><svg class="icon"><use href="#i-status"/></svg>Status</a>
<a class="n-domains" href="#domains"><svg class="icon"><use href="#i-domains"/></svg>Domains</a>
<a class="n-deliveries" href="#deliveries"><svg class="icon"><use href="#i-deliveries"/></svg>Deliveries</a>
<a class="n-queue g-only" href="#mail-queue"><svg class="icon"><use href="#i-queue"/></svg>Mail queue</a>
<a class="n-log g-only" href="#system-log"><svg class="icon"><use href="#i-log"/></svg>System log</a>
<a class="n-inbound g-only in-only" href="#inbound"><svg class="icon"><use href="#i-inbound"/></svg>Inbound</a>
<a class="n-dmarc g-only" href="#dmarc"><svg class="icon"><use href="#i-dmarc"/></svg>DMARC <span class="tag future">1.x</span></a>
<a class="n-backup g-only" href="#backup"><svg class="icon"><use href="#i-backup"/></svg>Backup</a>
<a class="n-users g-only" href="#users"><svg class="icon"><use href="#i-users"/></svg>Users</a>
<a class="n-help" href="#help"><svg class="icon"><use href="#i-help"/></svg>Help <span class="tag future">1.x</span></a>
</div>
<div class="session">
<span class="session-user muted"><svg class="icon"><use href="#i-account"/></svg>User: admin</span>
<a class="n-settings" href="#settings"><svg class="icon"><use href="#i-settings"/></svg>Settings</a>
<a class="btn-ghost" href="#login"><svg class="icon"><use href="#i-out"/></svg>Sign out</a>
</div>
</nav>
<div class="stage">
<div class="phone-bar">
<label class="nav-burger" for="nav-open" title="Menu"><svg class="icon"><use href="#i-menu"/></svg></label>
<img class="phone-mark" src="../selfpost-icon.svg" width="28" height="28" alt="">
<span class="grow">
<span class="pt pt-status">Status</span>
<span class="pt pt-domains">Domains</span>
<span class="pt pt-domain">example.com</span>
<span class="pt pt-deliveries">Deliveries</span>
<span class="pt pt-delivery">Message</span>
<span class="pt pt-queue">Mail queue</span>
<span class="pt pt-log">System log</span>
<span class="pt pt-inbound">Inbound</span>
<span class="pt pt-inbound-domain">lists.example.com</span>
<span class="pt pt-inbound-backup">backup.example.net</span>
<span class="pt pt-dmarc">DMARC</span>
<span class="pt pt-backup">Backup</span>
<span class="pt pt-users">Users</span>
<span class="pt pt-help">Help</span>
<span class="pt pt-settings">Settings</span>
</span>
<label class="help-link" for="help-index" title="Help">?</label>
</div>
<!-- STATUS -->
<main id="status" class="screen ops">
<div class="page-head">
<h1>Status</h1>
<label class="help-link" for="help-status" title="What these checks mean">?</label>
</div>
<div class="card" id="overall">
<h2>Overall <span class="st st-warn">warn</span></h2>
<p class="muted">Running, with warnings below.</p>
</div>
<div class="split">
<div class="card attn" id="queue">
<h2>Mail queue <span class="st st-warn">warn</span></h2>
<p>3 Kbytes in 3 Requests.</p>
<a class="btn" href="#mail-queue">View queue</a>
</div>
<div class="card" id="certificate">
<h2>TLS certificate <span class="st st-ok">ok</span></h2>
<label>Expires</label>
<span class="code">2026-11-02 12:00 UTC</span>
<p class="muted">Valid for another 78 day(s).</p>
</div>
</div>
<div class="split">
<div class="card" id="sockets">
<h2>Milter sockets <span class="st st-ok">ok</span></h2>
<table>
<thead><tr><th>Milter</th><th>State</th><th>Detail</th></tr></thead>
<tbody>
<tr>
<td>OpenDKIM</td>
<td><span class="st st-ok">ok</span></td>
<td class="muted">Listening</td>
</tr>
<tr>
<td>send-log</td>
<td><span class="st st-ok">ok</span></td>
<td class="muted">Listening</td>
</tr>
</tbody>
</table>
</div>
<div class="card" id="hostname">
<h2>Hostname and reverse DNS <span class="st st-ok">ok</span></h2>
<label>Server hostname</label>
<span class="code">mail.example.org</span>
<label>Forward and reverse lookup</label>
<span class="code">203.0.113.10 → mail.example.org</span>
<p class="muted">mail.example.org resolves to 203.0.113.10 and the reverse lookup points back at it.</p>
<form class="inline" action="#" onsubmit="return false"><button type="button">Re-check DNS</button></form>
</div>
</div>
<div class="card in-only" id="inbound-status">
<h2>Inbound <span class="st st-warn">warn</span></h2>
<p><code>INBOUND_RELAY_ENABLE</code> is on. Port 25 accepts mail for 2 domains and forwards it upstream — not to local mailboxes.</p>
<p class="muted">One domain has no MX pointing at this server. Recipients are a list or any address at the domain. Open Inbound for the list, MX checks, upstream, and recipient maps.</p>
<a href="#inbound">Inbound domains</a>
</div>
<div class="split">
<div class="card" id="machine">
<h2>Machine <span class="st st-ok">ok</span></h2>
<table>
<thead><tr><th class="metric">Resource</th><th>Usage</th><th>Detail</th></tr></thead>
<tbody>
<tr>
<td class="metric">CPU</td>
<td class="metric"><meter value="12" min="0" max="100" low="70" high="90" optimum="10">12%</meter> 12%</td>
<td class="muted">4 cores · 4 threads</td>
</tr>
<tr>
<td class="metric">Memory</td>
<td class="metric"><meter value="41" min="0" max="100" low="70" high="90" optimum="10">41%</meter> 41%</td>
<td class="muted">1.6 GiB used of 4.0 GiB.</td>
</tr>
<tr>
<td class="metric">Network</td>
<td class="metric">↓ 2.0 KiB/s<br>↑ 1.0 KiB/s</td>
<td class="muted">
<div>eth0: 1.0 MiB in, 512.0 KiB out</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="card" id="processes">
<h2>Processes <span class="st st-ok">ok</span></h2>
<table>
<thead><tr><th>Program</th><th>State</th><th>Detail</th></tr></thead>
<tbody>
<tr>
<td>opendkim</td>
<td><span class="st st-ok">RUNNING</span></td>
<td class="muted">pid 21, uptime 3 days, 4:12:01</td>
</tr>
<tr>
<td>panel</td>
<td><span class="st st-ok">RUNNING</span></td>
<td class="muted">pid 18, uptime 3 days, 4:12:03</td>
</tr>
<tr>
<td>postfix</td>
<td><span class="st st-ok">RUNNING</span></td>
<td class="muted">pid 42, uptime 3 days, 4:11:58</td>
</tr>
<tr>
<td>postfix-reload</td>
<td><span class="st st-ok">STOPPED</span></td>
<td class="muted">Not started</td>
</tr>
<tr>
<td>cert-reload</td>
<td><span class="st st-ok">STOPPED</span></td>
<td class="muted">Not started</td>
</tr>
<tr>
<td>logrotate</td>
<td><span class="st st-ok">STOPPED</span></td>
<td class="muted">Not started</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card" id="configuration">
<h2>Configuration</h2>
<p class="muted">Regenerates the OpenDKIM and Postfix configuration from the
database and reloads both daemons. Use it if you edited the files by hand,
restored a backup, or the running configuration looks out of step with the
domain and application lists. It does not touch the mail queue or the TLS
certificate, and it is safe to run at any time.</p>
<form class="inline" action="#" onsubmit="return false">
<button type="button">Reload configuration</button>
</form>
</div>
<p class="version">SelfPost 1.2.3 · © Mixeme · <a href="#">License (AGPL-3.0)</a></p>
</main>
<!-- LOGIN -->
<main id="login" class="screen auth">
<img class="mark" src="../selfpost-stamp.svg" width="330" height="150" alt="SelfPost">
<h1>Sign in</h1>
<div class="card narrow">
<form action="#status" onsubmit="location.hash='status'; return false;">
<label for="u">Username</label>
<input id="u" name="username" autocomplete="username">
<label for="p">Password</label>
<input id="p" name="password" type="password" autocomplete="current-password">
<button type="submit">Sign in</button>
</form>
</div>
<p class="version" style="text-align:center">© Mixeme · <a href="#">License (AGPL-3.0)</a></p>
</main>
<!-- SETUP -->
<main id="setup" class="screen auth">
<img class="mark" src="../selfpost-stamp.svg" width="330" height="150" alt="SelfPost">
<h1>Create administrator</h1>
<div class="card narrow">
<p class="muted">This one-time link creates the single panel administrator. After you submit, the link stops working for good.</p>
<form action="#status" onsubmit="location.hash='status'; return false;">
<label>Username</label>
<input autocomplete="username">
<label>Password</label>
<input type="password" autocomplete="new-password">
<label>Confirm password</label>
<input type="password" autocomplete="new-password">
<button type="submit">Create administrator</button>
</form>
</div>
</main>
<!-- DOMAINS -->
<main id="domains" class="screen ops">
<div class="page-head"><h1>Domains</h1></div>
<div class="card g-only" style="margin-bottom:1rem">
<h2>Add a sending domain</h2>
<form action="#" onsubmit="return false;">
<label for="dname">Domain</label>
<div class="input-row">
<input id="dname" placeholder="example.com" autocomplete="off">
<button type="button">Add domain</button>
</div>
</form>
</div>
<div class="card">
<h2>Domains</h2>
<table class="desk-only">
<thead><tr><th>Domain</th><th>DNS</th><th>Selector</th><th>Apps</th><th></th></tr></thead>
<tbody>
<tr>
<td><a href="#domain">example.com</a></td>
<td><a class="st st-ok" href="#domain">ok</a></td>
<td class="muted">mail</td>
<td>2</td>
<td class="actions g-only"><a class="danger" href="#domain-delete">Delete</a></td>
</tr>
<tr>
<td><a href="#domain">alerts.example.com</a></td>
<td><a class="st st-warn" href="#domain">warn</a></td>
<td class="muted">mail</td>
<td>1</td>
<td class="actions g-only"><a class="danger" href="#domain-delete">Delete</a></td>
</tr>
</tbody>
</table>
<ul class="phone-list phone-only">
<li><a href="#domain"><span>example.com</span><span class="st st-ok">ok</span><span class="pair">2 apps · selector mail</span></a></li>
<li><a href="#domain"><span>alerts.example.com</span><span class="st st-warn">warn</span><span class="pair">1 app · selector mail</span></a></li>
</ul>
<p class="muted">The DNS badge is the worst of DKIM, SPF and DMARC. Open a domain for details.</p>
</div>
</main>
<!-- DOMAIN DETAIL -->
<main id="domain" class="screen ops">
<h1>example.com</h1>
<a class="back" href="#domains">&larr; All domains</a>
<div class="card credential" id="new-credential">
<div class="card-head">
<h2>New application password</h2>
<label class="help-link" for="help-password" title="Why this is shown once">?</label>
</div>
<p class="muted">Shown <strong>once only</strong> and not stored. Copy it now.</p>
<label>Login</label>
<div class="code-row"><span class="code">newsletter</span><button type="button" class="copy">Copy</button></div>
<label>Password</label>
<div class="code-row"><span class="code">xK.9fQ2m-pL7wR</span><button type="button" class="copy">Copy</button></div>
</div>
<div class="card" id="dns-status">
<div class="card-head">
<h2>DNS status <span class="st st-ok">ok</span></h2>
<label class="help-link" for="help-dns" title="What these checks mean">?</label>
</div>
<div class="check-cols">
<div class="check-col">
<label>DKIM <span class="st st-ok">ok</span></label>
<div class="field-pair host-type"><div><label>Host / name</label><span class="code">mail._domainkey.example.com</span></div><div class="field-type"><label>Type</label><span class="code">TXT</span></div></div>
</div>
<div class="check-col">
<label>SPF <span class="st st-ok">ok</span></label>
<div class="field-pair host-type"><div><label>Host / name</label><span class="code">example.com</span></div><div class="field-type"><label>Type</label><span class="code">TXT</span></div></div>
</div>
<div class="check-col">
<label>DMARC <span class="st st-ok">ok</span></label>
<div class="field-pair host-type"><div><label>Host / name</label><span class="code">_dmarc.example.com</span></div><div class="field-type"><label>Type</label><span class="code">TXT</span></div></div>
<p class="muted">p=none; rua points at SelfPost ingest.</p>
</div>
<div class="check-col">
<label>Report authorization</label>
<p class="muted">Not required (rua= is on a domain SelfPost accepts).</p>
</div>
</div>
<form class="inline" action="#" onsubmit="return false"><button type="button">Re-check</button></form>
</div>
<div class="split">
<div class="card" id="dkim-spf">
<div class="card-head">
<h2>DKIM and SPF records</h2>
<label class="help-link" for="help-records" title="What to publish">?</label>
</div>
<p class="check-col-title">DKIM</p>
<div class="field-pair host-type"><div><label>Host / name</label><div class="code-row"><span class="code">mail._domainkey.example.com</span><button type="button" class="copy">Copy</button></div></div><div class="field-type"><label>Type</label><span class="code">TXT</span></div></div>
<label>Value</label>
<div class="code-row"><span class="code">v=DKIM1; k=rsa; p=MIIBIjANBgkqh…</span><button type="button" class="copy">Copy</button></div>
<p class="check-col-title">SPF</p>
<div class="code-row"><span class="code">v=spf1 ip4:203.0.113.10 -all</span><button type="button" class="copy">Copy</button></div>
</div>
<div class="card" id="dmarc-record">
<div class="card-head">
<h2>DMARC record</h2>
<label class="help-link" for="help-dmarc" title="What this record does">?</label>
</div>
<div class="field-pair host-type"><div><label>Host / name</label><div class="code-row"><span class="code">_dmarc.example.com</span><button type="button" class="copy">Copy</button></div></div><div class="field-type"><label>Type</label><span class="code">TXT</span></div></div>
<label>Value <span class="muted">(from Settings)</span></label>
<div class="code-row"><span class="code">v=DMARC1; p=none; rua=mailto:dmarc@mail.example.org</span><button type="button" class="copy">Copy</button></div>
<p class="muted"><a href="#dmarc">Open DMARC reports</a> for this domain.</p>
</div>
</div>
<div class="split">
<div class="card" id="connection">
<div class="card-head">
<h2>Connection settings</h2>
<label class="help-link" for="help-connection" title="How to connect">?</label>
</div>
<label>Server</label>
<div class="code-row"><span class="code">mail.example.org</span><button type="button" class="copy">Copy</button></div>
<label>Port and encryption</label>
<span class="code">465 — SSL/TLS (implicit)
587 — STARTTLS (submission)</span>
</div>
<div class="card" id="add-application">
<div class="card-head">
<h2>Add an application</h2>
<label class="help-link" for="help-apps" title="What an application is">?</label>
</div>
<form action="#" onsubmit="return false">
<label>Login</label>
<input placeholder="prod-server" autocomplete="off">
<label>Address mode</label>
<select><option>Any address of the domain</option><option>Specific addresses (list)</option></select>
<button type="button">Create application</button>
</form>
</div>
</div>
<div class="card" id="applications">
<div class="card-head">
<h2>Applications</h2>
<label class="help-link" for="help-apps" title="Address mode and rate limits">?</label>
</div>
<ul class="apps">
<li class="app-item">
<p class="app-login">newsletter</p>
<p class="app-addr muted">Any address of the domain — *@example.com</p>
<div class="actions">
<input class="panel-toggle t-edit" id="edit-1" type="checkbox" checked>
<label class="toggle for-edit" for="edit-1">Edit <span class="st st-ok">limit</span></label>
<button type="button">New password</button>
<button type="button" class="danger">Delete</button>
<div class="panel panel-edit">
<div class="check-cols">
<div class="check-col">
<p class="check-col-title">Address mode</p>
<select><option selected>Any address of the domain</option><option>Specific addresses (list)</option></select>
</div>
<div class="check-col">
<p class="check-col-title">Trusted-IP override <span class="st st-ok">active</span></p>
<textarea rows="1">203.0.113.40</textarea>
<div class="field-pair">
<div><label>Message limit</label><input value="80"></div>
<div><label>Window (seconds)</label><input value="60"></div>
</div>
</div>
</div>
</div>
</div>
</li>
<li class="app-item">
<p class="app-login">billing</p>
<p class="app-addr muted">Fixed list — invoices@example.com</p>
<div class="actions">
<button type="button">Edit</button>
<button type="button">New password</button>
<button type="button" class="danger">Delete</button>
</div>
</li>
</ul>
</div>
<div class="card" id="domain-settings">
<div class="card-head">
<h2>Domain settings</h2>
<label class="help-link" for="help-domain-settings" title="Reports and rate limits">?</label>
</div>
<div class="check-cols">
<div class="check-col">
<p class="check-col-title">DMARC reports</p>
<label>Aggregate reports (rua=)</label>
<select><option selected>Same as Settings (dmarc@mail.example.org)</option><option>No aggregate reports</option><option>Custom address</option></select>
<button type="button">Save DMARC report settings</button>
</div>
<div class="check-col">
<p class="check-col-title">Level-2 rate limit <span class="st st-ok">active</span></p>
<div class="field-pair">
<div><label>Message limit</label><input value="40"></div>
<div><label>Window (seconds)</label><input value="60"></div>
</div>
<button type="button">Save limit</button>
</div>
</div>
</div>
<div class="split">
<div class="card" id="export-domain">
<div class="card-head">
<h2>Export domain</h2>
<label class="help-link" for="help-export" title="What the file contains">?</label>
</div>
<p class="muted"><strong>Secret file</strong> — transfer securely, or encrypt as <code>.spde</code>.</p>
<div class="encrypt">
<label class="check"><input type="checkbox"> <span>Encrypt with a password</span></label>
<div class="encrypt-fields">
<label>Password</label><input type="password">
<label>Repeat password</label><input type="password">
</div>
</div>
<button type="button">Export domain</button>
</div>
<div class="card g-only">
<h2>Danger zone</h2>
<p class="muted">Deletes the DKIM key and every application on this domain.</p>
<a class="danger" href="#domain-delete">Delete domain</a>
</div>
</div>
</main>
<!-- DOMAIN DELETE -->
<main id="domain-delete" class="screen form">
<h1>Delete example.com</h1>
<a class="back" href="#domain">&larr; Back to example.com</a>
<div class="card">
<h2>Confirm deletion</h2>
<p>You are about to delete <strong>example.com</strong>. This will:</p>
<ul>
<li>permanently delete its DKIM signing key;</li>
<li>delete <strong>all 2 bound applications</strong>, including their SASL credentials;</li>
<li>reload OpenDKIM so the domain is no longer signed.</li>
</ul>
<p class="muted">This cannot be undone.</p>
<button type="button" class="danger">Delete example.com</button>
</div>
</main>
<!-- DELIVERIES -->
<main id="deliveries" class="screen ops">
<h1>Deliveries</h1>
<form class="toolbar" action="#" onsubmit="return false">
<div class="field"><label>Domain</label>
<select><option>All domains</option><option>example.com</option><option>alerts.example.com</option></select>
</div>
<div class="field"><label>Application</label>
<select><option>All applications</option><option>newsletter</option><option>billing</option></select>
</div>
<button type="button">Filter</button>
</form>
<div class="card">
<table class="desk-only">
<thead><tr><th>Time</th><th>From</th><th>To</th><th>Subject</th><th>Status</th><th></th></tr></thead>
<tbody>
<tr>
<td class="time muted">2026-08-15 20:14:02</td>
<td>billing@example.com</td>
<td>ada@example.net</td>
<td class="subject"><span>Invoice #4412</span></td>
<td class="status"><span class="st st-warn">deferred</span></td>
<td class="actions"><a href="#delivery">Details</a></td>
</tr>
<tr>
<td class="time muted">2026-08-15 20:11:40</td>
<td>news@example.com</td>
<td>list-bounces@example.net</td>
<td class="subject"><span>August digest</span></td>
<td class="status"><span class="st st-ok">delivered</span></td>
<td class="actions"><a href="#delivery">Details</a></td>
</tr>
<tr>
<td class="time muted">2026-08-15 19:02:11</td>
<td>alerts@alerts.example.com</td>
<td>noreply@blocked.example</td>
<td class="subject"><span>Disk 92% on web-3</span></td>
<td class="status"><span class="st st-error">bounced</span></td>
<td class="actions"><a href="#delivery">Details</a></td>
</tr>
<tr>
<td class="time muted">2026-08-15 18:44:09</td>
<td>news@example.com</td>
<td>sam@example.org</td>
<td class="subject"><span>August digest</span></td>
<td class="status"><span class="st st-ok">delivered</span></td>
<td class="actions"><a href="#delivery">Details</a></td>
</tr>
</tbody>
</table>
<ul class="phone-list phone-only">
<li>
<a href="#delivery">
<span class="st st-warn">deferred</span><span class="when">20:14</span>
<span class="pair">billing@example.com → ada@example.net</span>
<span class="subj">Invoice #4412</span>
</a>
</li>
<li>
<a href="#delivery">
<span class="st st-ok">delivered</span><span class="when">20:11</span>
<span class="pair">news@example.com → list-bounces@example.net</span>
<span class="subj">August digest</span>
</a>
</li>
<li>
<a href="#delivery">
<span class="st st-error">bounced</span><span class="when">19:02</span>
<span class="pair">alerts@alerts.example.com → noreply@blocked.example</span>
<span class="subj">Disk 92% on web-3</span>
</a>
</li>
</ul>
<p class="muted">Page 1 of 4 · <a href="#deliveries">Older →</a></p>
</div>
</main>
<!-- DELIVERY (deferred + queue-retries) -->
<main id="delivery" class="screen ops">
<h1 class="subject">Invoice #4412</h1>
<p class="route">
<span class="addr">billing@example.com</span>
<span class="arrow"></span>
<span class="addr">ada@example.net</span>
<span class="st st-warn">deferred</span>
</p>
<a class="back" href="#deliveries">&larr; Back to deliveries</a>
<div class="split">
<div class="card">
<h2>Message</h2>
<div class="facts">
<div class="fact"><span class="fact-label">Domain</span><span class="fact-value">example.com</span></div>
<div class="fact"><span class="fact-label">Application</span><span class="fact-value">billing</span></div>
<div class="fact"><span class="fact-label">Accepted</span><span class="fact-value">2026-08-15 20:14:02 UTC</span></div>
<div class="fact"><span class="fact-label">Status reported</span><span class="fact-value">2026-08-15 20:14:08 UTC</span></div>
<div class="fact"><span class="fact-label">Queue id</span><span class="fact-value mono">4C3A1E2F1A</span></div>
<div class="fact"><span class="fact-label">Journal id</span><span class="fact-value mono">1842</span></div>
</div>
</div>
<div class="card">
<h2>History</h2>
<ol class="timeline">
<li class="event lvl-ok">
<p class="event-time">2026-08-15 20:14:02 UTC</p>
<p class="event-title"><span class="st st-ok">accepted</span> Received by the relay</p>
<p class="event-detail muted">SASL login billing, queued as 4C3A1E2F1A.</p>
</li>
<li class="event lvl-warn">
<p class="event-time">2026-08-15 20:14:08 UTC</p>
<p class="event-title"><span class="st st-warn">deferred</span> Receiving MX asked to try later</p>
<p class="event-detail muted">Postfix retries: first after 5 minutes, then with increasing gaps up to 1 hour 7 minutes, for up to 5 days. There is no fixed attempt count — a deferred message stays in the queue until it is delivered or that lifetime runs out.</p>
</li>
<li class="event pending">
<p class="event-time">not yet</p>
<p class="event-title"><span class="st st-unknown">delivery</span> Waiting on the next retry</p>
</li>
</ol>
</div>
</div>
<div class="card">
<h2>Delivery log</h2>
<table class="log desk-only">
<thead><tr><th>Time</th><th>Message</th></tr></thead>
<tbody>
<tr><td class="time muted">20:14:02</td><td class="log-text">postfix/smtpd[221]: 4C3A1E2F1A: client=203.0.113.40, sasl_username=billing</td></tr>
<tr><td class="time muted">20:14:02</td><td class="log-text">postfix/cleanup[224]: 4C3A1E2F1A: message-id=&lt;4412@example.com&gt;</td></tr>
<tr><td class="time muted">20:14:08</td><td class="log-text">postfix/smtp[230]: 4C3A1E2F1A: to=&lt;ada@example.net&gt;, relay=mx.example.net[198.51.100.20]:25, delay=6, status=deferred (450 4.2.1 mailbox busy)</td></tr>
</tbody>
</table>
<div class="phone-only">
<p class="muted" style="margin:0.6rem 0 0.2rem">20:14:08</p>
<span class="code">status=deferred (450 4.2.1 mailbox busy)</span>
<p class="muted" style="margin:0.6rem 0 0.2rem">20:14:02</p>
<span class="code">client=203.0.113.40, sasl_username=billing</span>
</div>
</div>
</main>
<!-- MAIL QUEUE + queue-retries -->
<main id="mail-queue" class="screen ops">
<h1>Mail queue</h1>
<div class="card">
<h2>How delivery retries work</h2>
<p class="muted">This Postfixs policy, read once at panel start. There is no maximum attempt count — only time.</p>
<div class="facts retry-facts">
<div class="fact"><span class="fact-label">First retry</span><span class="fact-value">5 minutes</span></div>
<div class="fact"><span class="fact-label">Later retries</span><span class="fact-value">doubling, cap 1 h 7 min</span></div>
<div class="fact"><span class="fact-label">Kept in queue</span><span class="fact-value">5 days</span></div>
<div class="fact"><span class="fact-label">Then</span><span class="fact-value">bounced</span></div>
</div>
</div>
<div class="card">
<h2>Pending messages</h2>
<table class="desk-only">
<thead><tr><th>Queue id</th><th>Age</th><th>From</th><th>To</th><th>Size</th></tr></thead>
<tbody>
<tr><td class="mono">4C3A1E2F1A</td><td class="time muted">18 min</td><td>billing@example.com</td><td>ada@example.net</td><td>12 KiB</td></tr>
<tr><td class="mono">4C3A1E3010</td><td class="time muted">11 min</td><td>news@example.com</td><td>pat@slow.example</td><td>48 KiB</td></tr>
<tr><td class="mono">4C3A1E3102</td><td class="time muted">4 min</td><td>billing@example.com</td><td>ada@example.net</td><td>9 KiB</td></tr>
</tbody>
</table>
<ul class="phone-list phone-only">
<li><a href="#delivery"><span>4C3A1E2F1A</span><span class="when">18 min</span><span class="pair">billing@example.com → ada@example.net</span></a></li>
<li><a href="#delivery"><span>4C3A1E3010</span><span class="when">11 min</span><span class="pair">news@example.com → pat@slow.example</span></a></li>
<li><a href="#delivery"><span>4C3A1E3102</span><span class="when">4 min</span><span class="pair">billing@example.com → ada@example.net</span></a></li>
</ul>
<label>postqueue -p</label>
<span class="code">-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
4C3A1E2F1A* 12288 Sat Aug 15 20:14:02 billing@example.com
ada@example.net
4C3A1E3010 49152 Sat Aug 15 20:21:18 news@example.com
pat@slow.example
-- 3 Kbytes in 3 Requests.</span>
</div>
</main>
<!-- SYSTEM LOG -->
<main id="system-log" class="screen ops">
<h1>System log</h1>
<div class="card">
<h2>Recent log entries</h2>
<span class="code">Aug 15 20:14:08 mail postfix/smtp[230]: 4C3A1E2F1A: to=&lt;ada@example.net&gt;, relay=mx.example.net[198.51.100.20]:25, delay=6, delays=0.2/0.1/0.4/5.3, dsn=4.2.1, status=deferred (450 4.2.1 mailbox busy)
Aug 15 20:14:02 mail postfix/qmgr[119]: 4C3A1E2F1A: from=&lt;billing@example.com&gt;, size=12288, nrcpt=1 (queue active)
Aug 15 20:14:02 mail postfix/smtpd[221]: 4C3A1E2F1A: client=203.0.113.40[203.0.113.40], sasl_method=PLAIN, sasl_username=billing
Aug 15 20:11:40 mail postfix/smtp[228]: 4B19D0AA01: to=&lt;list-bounces@example.net&gt;, relay=mx.example.net[198.51.100.20]:25, delay=0.9, status=sent (250 2.0.0 Ok)
Aug 15 20:02:11 mail postfix/smtp[226]: 4B19C0BB12: to=&lt;noreply@blocked.example&gt;, status=bounced (host mx.blocked.example[203.0.113.99] said: 550 5.7.1 rejected)</span>
</div>
</main>
<!-- BACKUP -->
<main id="backup" class="screen ops">
<h1>Backup &amp; migration</h1>
<div class="split">
<div class="card">
<h2>Full backup</h2>
<p class="muted">Download a full backup of all persistent state — the database, every domains DKIM key and the application credentials. Restore into a container of the <strong>same SelfPost version</strong>, with the same data mount, before first start. TLS certificates and the mail queue are not included.</p>
<p class="muted"><strong>The backup file is a secret.</strong> Encrypting it is the simplest way to store it: the download is then a <code>.spbk</code> that only the password opens.</p>
<div class="encrypt">
<label class="check"><input type="checkbox" checked> <span>Encrypt with a password</span></label>
<div class="encrypt-fields">
<label>Password</label><input type="password" placeholder="at least 8 characters">
<label>Repeat password</label><input type="password">
<p class="muted">Keep this password: without it the file cannot be opened.</p>
</div>
</div>
<button type="button">Download full backup</button>
</div>
<div class="card">
<h2>Import a domain</h2>
<p class="muted">Move a single domain here from another SelfPost instance — plain <code>.json</code> or encrypted <code>.spde</code>. Its DKIM key and application passwords come across, so the published DNS record needs no change. The export file is a secret, like a full backup.</p>
<label>Domain export file</label>
<input type="file">
<label>Password</label>
<input type="password" autocomplete="off">
<p class="muted">Needed for a <code>.spde</code> file. Leave empty for plain <code>.json</code>.</p>
<button type="button">Import domain</button>
</div>
</div>
</main>
<!-- USERS -->
<main id="users" class="screen form">
<h1>Users</h1>
<div class="card">
<p><a href="#user-form">Create user</a></p>
<table>
<thead><tr><th>Username</th><th>Role</th><th>Domains</th><th></th></tr></thead>
<tbody>
<tr><td>admin</td><td>Global</td><td class="muted">All</td><td class="actions"><a href="#user-form">Edit</a></td></tr>
<tr><td>ops-alerts</td><td>Domain admin</td><td class="muted">alerts.example.com</td><td class="actions"><a href="#user-form">Edit</a></td></tr>
</tbody>
</table>
</div>
</main>
<!-- USER FORM -->
<main id="user-form" class="screen form">
<h1>Edit user</h1>
<a class="back" href="#users">&larr; Back to users</a>
<div class="card narrow">
<form action="#" onsubmit="return false">
<label>Username</label>
<input value="ops-alerts">
<label>Password (leave empty to keep)</label>
<input type="password">
<label>Role</label>
<select><option selected>Domain administrator</option><option>Global administrator</option></select>
<fieldset>
<legend>Assigned domains</legend>
<p class="muted">Required for domain administrators.</p>
<label class="check"><input type="checkbox"> example.com</label>
<label class="check"><input type="checkbox" checked> alerts.example.com</label>
</fieldset>
<div class="form-actions">
<button type="button">Save</button>
<a class="danger" href="#user-delete">Delete user</a>
</div>
</form>
</div>
</main>
<!-- USER DELETE -->
<main id="user-delete" class="screen form">
<h1>Delete ops-alerts</h1>
<a class="back" href="#user-form">&larr; Back to ops-alerts</a>
<div class="card narrow">
<h2>Confirm deletion</h2>
<p>You are about to delete the panel user <strong>ops-alerts</strong>. A signed-in session for this user stops working immediately.</p>
<button type="button" class="danger">Delete ops-alerts</button>
</div>
</main>
<!-- SETTINGS -->
<main id="settings" class="screen ops">
<h1>Settings</h1>
<form action="#" onsubmit="return false">
<div class="split">
<div class="card">
<h2>Panel credentials</h2>
<p class="muted">These are the credentials for this control panel only. Applications keep their own logins and passwords, which are not affected.</p>
<label>Username</label><input value="admin" autocomplete="username">
<label>Current password</label><input type="password" autocomplete="current-password">
<label>New password</label><input type="password" autocomplete="new-password">
<label>Confirm new password</label><input type="password">
</div>
<div class="card g-only">
<h2>DMARC aggregate reports</h2>
<p class="muted">Default <code>rua=</code> for every sending domain (overridable per domain). When ingest is on, this can be an address SelfPost accepts.</p>
<label>Default report address</label>
<input type="email" value="dmarc@mail.example.org">
<p class="muted">When <code>rua=</code> points at another domain, that hub must publish a report-authorisation record. <a href="#dmarc">DMARC reports</a> in the panel.</p>
<div class="field-pair host-type">
<div>
<label>Host / name</label>
<div class="code-row"><span class="code">mail.example.org._report._dmarc.example.com</span><button type="button" class="copy">Copy</button></div>
</div>
<div class="field-type">
<label>Type</label>
<span class="code">TXT</span>
</div>
</div>
<label>Value</label>
<div class="code-row"><span class="code">v=DMARC1;</span><button type="button" class="copy">Copy</button></div>
<label>Report authorization DNS <span class="st st-ok">ok</span></label>
<p class="muted">Published at mail.example.org._report._dmarc.example.com — aggregate reports addressed to dmarc@mail.example.org are authorised.</p>
</div>
</div>
<button type="button">Save changes</button>
<p class="muted">Leave both new-password fields empty to change the username or DMARC address only. Changing the password signs out every other session; this one stays signed in.</p>
</form>
<div class="card" id="rate-limits">
<h2>Sending rate limits</h2>
<p class="muted">Level 1 is set in Compose; restart the container to change it. Domain and application ceilings live on each domains page.</p>
<div class="check-cols">
<div class="check-col">
<p class="check-col-title">Level 1 — per client IP</p>
<span class="code">100 messages / 60 seconds</span>
<p class="muted"><code>RATE_LIMIT_MESSAGES_PER_IP</code> / <code>RATE_LIMIT_WINDOW_SECONDS</code>. Hard ceiling for every connecting IP; the panel cannot raise a domain or application limit above this.</p>
</div>
<div class="check-col">
<p class="check-col-title">Level 2 — domain</p>
<p class="muted">Optional ceiling for <em>all</em> senders on a domain. When unset, only level 1 applies. Must be ≤ level 1.</p>
</div>
<div class="check-col">
<p class="check-col-title">Level 2 — application</p>
<p class="muted">Optional override for trusted IPs: a ceiling strictly above the domain limit (still ≤ level 1). Those IPs skip the domain check; everyone else stays under the domain (or level 1).</p>
</div>
</div>
</div>
</main>
<!-- INBOUND -->
<main id="inbound" class="screen ops">
<div class="page-head">
<h1>Inbound <span class="tag">1.x</span></h1>
</div>
<p class="muted">Backup-MX / forwarder. Accepts on port 25 only for listed domains. Recipients are either an allow-list or any address at that domain. Off by default in Compose.</p>
<div class="card" style="margin-bottom:1rem">
<h2>Add inbound domain</h2>
<form action="#" onsubmit="return false">
<label>Domain</label>
<div class="input-row">
<input placeholder="lists.example.com">
<button type="button">Add domain</button>
</div>
</form>
</div>
<div class="card">
<h2>Forwarding</h2>
<table class="desk-only">
<thead><tr><th>Domain</th><th>DNS</th><th>Upstream</th><th>Recipients</th><th>TLS</th><th></th></tr></thead>
<tbody>
<tr>
<td><a href="#inbound-domain">lists.example.com</a></td>
<td><a class="st st-ok" href="#inbound-domain">ok</a></td>
<td class="muted">10.0.0.8:25</td>
<td>12 listed</td>
<td><span class="st st-ok">required</span></td>
<td class="actions"><a class="danger" href="#inbound-delete">Delete</a></td>
</tr>
<tr>
<td><a href="#inbound-backup">backup.example.net</a></td>
<td><a class="st st-error" href="#inbound-backup">error</a></td>
<td class="muted">192.0.2.20:25</td>
<td>any</td>
<td><span class="st st-unknown">off</span></td>
<td class="actions"><a class="danger" href="#inbound-delete">Delete</a></td>
</tr>
</tbody>
</table>
<ul class="phone-list phone-only">
<li><a href="#inbound-domain"><span>lists.example.com</span><span class="st st-ok">ok</span><span class="pair">12 listed · 10.0.0.8:25</span></a></li>
<li><a href="#inbound-backup"><span>backup.example.net</span><span class="st st-error">error</span><span class="pair">any recipient · 192.0.2.20:25</span></a></li>
</ul>
<p class="muted">The <em>DNS</em> badge is the MX check: at least one MX must point at this server. Results are cached for a few minutes; open a domain for the lookup and a <em>Re-check</em> button.</p>
</div>
</main>
<!-- INBOUND DOMAIN -->
<main id="inbound-domain" class="screen ops">
<h1>lists.example.com</h1>
<a class="back" href="#inbound">&larr; All inbound domains</a>
<div class="card">
<h2>DNS status <span class="st st-ok">ok</span></h2>
<p class="muted">Cached a few minutes — use <em>Re-check</em> after publishing. Unlike outbound, inbound needs an MX pointing at this server.</p>
<div class="check-cols">
<div class="check-col">
<label>MX <span class="st st-ok">ok</span></label>
<div class="field-pair host-type">
<div>
<label>Host / name</label>
<span class="code">lists.example.com</span>
</div>
<div class="field-type">
<label>Type</label>
<span class="code">MX</span>
</div>
</div>
<label>Value</label>
<span class="code">10 mail.example.org.
20 mail.primary.example.net.</span>
<p class="muted">An MX points at mail.example.org (this server). Other MX values are the domains own primaries — they are not an error.</p>
</div>
</div>
<form class="inline" action="#" onsubmit="return false"><button type="button">Re-check</button></form>
</div>
<div class="split">
<div class="card">
<h2>Upstream</h2>
<p class="muted">Where accepted mail is handed off. Not a mailbox.</p>
<label>Host</label><input value="10.0.0.8">
<label>Port</label><input value="25">
<label>TLS to upstream</label>
<select><option>Opportunistic</option><option selected>Required</option><option>Off</option></select>
<button type="button">Save upstream</button>
</div>
<div class="card">
<h2>MX record to publish</h2>
<p class="muted">Add this MX so the internet delivers here. Keep any existing primary MX if this is backup-MX.</p>
<div class="field-pair host-type">
<div>
<label>Host / name</label>
<div class="code-row"><span class="code">lists.example.com</span><button type="button" class="copy">Copy</button></div>
</div>
<div class="field-type">
<label>Type</label>
<span class="code">MX</span>
</div>
</div>
<label>Value</label>
<div class="code-row"><span class="code">10 mail.example.org.</span><button type="button" class="copy">Copy</button></div>
</div>
</div>
<div class="split">
<div class="card rcpt-mode">
<h2>Valid recipients</h2>
<p class="muted">Who this domain accepts on port 25. Same idea as an applications address mode: a list, or any address at the domain.</p>
<label>Who to accept</label>
<select>
<option value="list" selected>Listed addresses only</option>
<option value="any">Any recipient at this domain</option>
</select>
<div class="rcpt-list">
<label>Addresses (one per line or comma-separated)</label>
<textarea rows="6">staff@lists.example.com
postmaster@lists.example.com
abuse@lists.example.com</textarea>
<p class="muted">Unknown recipients are rejected at RCPT so this relay does not generate backscatter.</p>
</div>
<p class="rcpt-any muted">Every address at this domain is accepted and forwarded. Prefer a list unless the upstream rejects unknowns — otherwise this relay may generate backscatter.</p>
<button type="button">Save recipients</button>
</div>
<div class="card">
<h2>Danger zone</h2>
<p class="muted">Stops accepting mail for this domain. Does not touch outbound sending domains.</p>
<a class="danger" href="#inbound-delete">Delete inbound domain</a>
</div>
</div>
</main>
<!-- INBOUND DOMAIN (MX error) -->
<main id="inbound-backup" class="screen ops">
<h1>backup.example.net</h1>
<a class="back" href="#inbound">&larr; All inbound domains</a>
<div class="card">
<h2>DNS status <span class="st st-error">error</span></h2>
<p class="muted">Cached a few minutes — use <em>Re-check</em> after publishing. Unlike outbound, inbound needs an MX pointing at this server.</p>
<div class="check-cols">
<div class="check-col">
<label>MX <span class="st st-error">error</span></label>
<div class="field-pair host-type">
<div>
<label>Host / name</label>
<span class="code">backup.example.net</span>
</div>
<div class="field-type">
<label>Type</label>
<span class="code">MX</span>
</div>
</div>
<label>Value</label>
<span class="code">10 mail.primary.example.net.</span>
<p class="error">No MX points at mail.example.org (this server). Publish the record below, or wait for DNS to propagate and Re-check.</p>
</div>
</div>
<form class="inline" action="#" onsubmit="return false"><button type="button">Re-check</button></form>
</div>
<div class="split">
<div class="card">
<h2>Upstream</h2>
<p class="muted">Where accepted mail is handed off. Not a mailbox.</p>
<label>Host</label><input value="192.0.2.20">
<label>Port</label><input value="25">
<label>TLS to upstream</label>
<select><option>Opportunistic</option><option>Required</option><option selected>Off</option></select>
<button type="button">Save upstream</button>
</div>
<div class="card">
<h2>MX record to publish</h2>
<p class="muted">Add this MX so the internet delivers here. Keep any existing primary MX if this is backup-MX.</p>
<div class="field-pair host-type">
<div>
<label>Host / name</label>
<div class="code-row"><span class="code">backup.example.net</span><button type="button" class="copy">Copy</button></div>
</div>
<div class="field-type">
<label>Type</label>
<span class="code">MX</span>
</div>
</div>
<label>Value</label>
<div class="code-row"><span class="code">20 mail.example.org.</span><button type="button" class="copy">Copy</button></div>
</div>
</div>
<div class="split">
<div class="card rcpt-mode">
<h2>Valid recipients</h2>
<p class="muted">Who this domain accepts on port 25. Same idea as an applications address mode: a list, or any address at the domain.</p>
<label>Who to accept</label>
<select>
<option value="list">Listed addresses only</option>
<option value="any" selected>Any recipient at this domain</option>
</select>
<div class="rcpt-list">
<label>Addresses (one per line or comma-separated)</label>
<textarea rows="6">postmaster@backup.example.net
abuse@backup.example.net</textarea>
<p class="muted">Unknown recipients are rejected at RCPT so this relay does not generate backscatter.</p>
</div>
<p class="rcpt-any muted">Every address at this domain is accepted and forwarded. Prefer a list unless the upstream rejects unknowns — otherwise this relay may generate backscatter.</p>
<button type="button">Save recipients</button>
</div>
<div class="card">
<h2>Danger zone</h2>
<p class="muted">Stops accepting mail for this domain. Does not touch outbound sending domains.</p>
<a class="danger" href="#inbound-delete">Delete inbound domain</a>
</div>
</div>
</main>
<!-- INBOUND DELETE -->
<main id="inbound-delete" class="screen form">
<h1>Delete lists.example.com</h1>
<a class="back" href="#inbound-domain">&larr; Back to lists.example.com</a>
<div class="card">
<h2>Confirm deletion</h2>
<p>You are about to stop accepting inbound mail for <strong>lists.example.com</strong>. This will:</p>
<ul>
<li>remove it from <code>relay_domains</code> and the recipient map;</li>
<li>stop forwarding to <strong>10.0.0.8:25</strong>;</li>
<li>leave outbound sending domains untouched.</li>
</ul>
<p class="muted">This cannot be undone from a backup of inbound maps alone unless you restore one. Remove the MX if you do not plan to re-add the domain.</p>
<form action="#" onsubmit="return false">
<button type="button" class="danger">Delete lists.example.com</button>
</form>
</div>
</main>
<!-- DMARC (candidate) -->
<main id="dmarc" class="screen ops">
<div class="page-head">
<h1>DMARC reports <span class="tag future">candidate</span></h1>
</div>
<p class="muted">Aggregate reports SelfPost accepted for <code>rua=</code>. Forensic (<code>ruf=</code>) is out of scope.</p>
<div class="status-grid" style="margin-bottom:1rem">
<article class="card">
<h2>Ingest <span class="st st-ok">ok</span></h2>
<p>Last report 6 hours ago. 14 kept, 0 parse failures this week.</p>
</article>
<article class="card">
<h2>example.com <span class="st st-ok">pass</span></h2>
<p>98% aligned last 7 days. Tightening <code>p=</code> looks reasonable.</p>
</article>
<article class="card attn">
<h2>alerts.example.com <span class="st st-warn">fail</span></h2>
<p>A third-party sender is not in SPF/DKIM. See sources.</p>
</article>
</div>
<div class="card">
<h2>Sources · last 7 days</h2>
<table class="desk-only">
<thead><tr><th>Domain</th><th>Source</th><th>Pass</th><th>Fail</th><th>Disposition</th></tr></thead>
<tbody>
<tr><td>example.com</td><td class="muted">203.0.113.10 (this relay)</td><td>412</td><td>2</td><td><span class="st st-ok">none</span></td></tr>
<tr><td>example.com</td><td class="muted">google.com / 66.102.0.0/20</td><td>0</td><td>6</td><td><span class="st st-warn">none</span></td></tr>
<tr><td>alerts.example.com</td><td class="muted">203.0.113.10 (this relay)</td><td>88</td><td>0</td><td><span class="st st-ok">none</span></td></tr>
<tr><td>alerts.example.com</td><td class="muted">unknown / 198.51.100.80</td><td>0</td><td>19</td><td><span class="st st-error">none</span></td></tr>
</tbody>
</table>
<ul class="phone-list phone-only">
<li><a href="#domain"><span>example.com · this relay</span><span class="st st-ok">412 pass</span></a></li>
<li><a href="#domain"><span>alerts.example.com · 198.51.100.80</span><span class="st st-error">19 fail</span></a></li>
</ul>
</div>
</main>
<!-- HELP (candidate) -->
<main id="help" class="screen form">
<div class="page-head">
<h1>Help <span class="tag future">candidate</span></h1>
</div>
<p class="muted">Short operator notes inside the panel — not a second copy of the full guide. Seeded from the Status explanations that do not belong on the cards (what a kernel counter is, why PTR is set at the host, what Reload does not touch). The cards themselves keep their readings, Detail columns, and the Configuration control.</p>
<div class="card">
<h2>On this panel</h2>
<ul>
<li><a href="#help">Machine</a> — kernel counters and the rate window</li>
<li><a href="#help">TLS certificate</a> — port 465, reverse-proxy mount</li>
<li><a href="#help">Hostname / reverse DNS</a> — forward-confirmed PTR at the hosting provider</li>
<li><a href="#help">Mail queue retries</a> — time-based, no attempt budget</li>
<li><a href="#help">Inbound</a> — not mailboxes; listed recipients or any address at the domain</li>
<li>Domain page — DNS, records, connection, applications, export (drawer from each cards «?»)</li>
</ul>
<p class="muted">The same texts open in the drawer from Statuss «?» — so a card can stay a reading, not a paragraph, without throwing the reading away.</p>
</div>
<div class="card">
<h2>Machine</h2>
<p>CPU and memory are the containers own readings, not the hosts spare capacity. Network is a short window, not a daily total. High CPU with an empty queue usually means something else on the box — not SelfPost “being slow to send”.</p>
</div>
<div class="card">
<h2>TLS certificate</h2>
<p>Port 465 presents the certificate the reverse proxy (or the image) mounted. The panel does not issue certificates. A warn here is “expires soon”; an error is “missing or unreadable”, and clients will refuse submission.</p>
</div>
<div class="card">
<h2>Hostname / reverse DNS</h2>
<p>Forward-confirmed reverse DNS: the A/AAAA for <code>SELFPOST_HOSTNAME</code> must reverse to that same name. PTR is set at the hosting provider, not in this panel. Receiving networks use this pair as a cheap reputation check.</p>
</div>
</main>
</div>
</div>
<label class="help-scrim" for="help-off"></label>
<aside class="help-drawer">
<label class="help-close" for="help-off">Close</label>
<article class="help-pane help-pane-index">
<h2>Help</h2>
<p>Short notes for the card you opened — not a second copy of the guide.</p>
<p class="muted">Status</p>
<ul class="toc">
<li><label for="help-status">Status checks</label></li>
</ul>
<p class="muted">Domain</p>
<ul class="toc">
<li><label for="help-password">New application password</label></li>
<li><label for="help-dns">DNS status</label></li>
<li><label for="help-records">DKIM and SPF records</label></li>
<li><label for="help-dmarc">DMARC record</label></li>
<li><label for="help-connection">Connection settings</label></li>
<li><label for="help-apps">Applications</label></li>
<li><label for="help-domain-settings">Domain settings</label></li>
<li><label for="help-export">Export domain</label></li>
</ul>
</article>
<article class="help-pane help-pane-status">
<h2>Status checks</h2>
<p>The cards keep the readings. This drawer is what used to sit under them as paragraphs.</p>
<h2>Machine</h2>
<p>CPU and memory are the containers readings. Network is a short rate window. These numbers explain load; they do not replace the queue.</p>
<h2>TLS certificate</h2>
<p>Presented on port 465. Issued and renewed outside SelfPost. Warn = expires soon; error = missing, and submission will fail.</p>
<h2>Hostname / reverse DNS</h2>
<p>The hostname must forward to this IP and the PTR must come back to the same name. Set PTR at the provider.</p>
<h2>Mail queue</h2>
<p>Deferred mail is retried on a time schedule (first delay, doubling cap, queue lifetime). There is no “attempt 3 of N”.</p>
<p class="more muted"><label for="help-index">All topics</label></p>
</article>
<article class="help-pane help-pane-password">
<h2>New application password</h2>
<p>Shown <strong>once only</strong> and not stored. Copy it now — if it is lost, regenerate a new one. The previous password stops working immediately.</p>
<p class="more muted"><label for="help-index">All topics</label></p>
</article>
<article class="help-pane help-pane-dns">
<h2>DNS status</h2>
<p>The badge is the worst of DKIM, SPF and DMARC. Results are cached a few minutes — use <em>Re-check</em> after publishing.</p>
<p>SPF is a shallow check: the literal address only, no <code>include:</code> or <code>redirect=</code>. Report authorization is required only when <code>rua=</code> points at a domain this server does not accept.</p>
<p class="more muted"><label for="help-index">All topics</label></p>
</article>
<article class="help-pane help-pane-records">
<h2>DKIM and SPF records</h2>
<p>DKIM is not a secret. The selector on this page is the one this server signs with. Merge the SPF example into an existing record if the domain already has one — do not publish a second TXT.</p>
<p class="more muted"><label for="help-index">All topics</label></p>
</article>
<article class="help-pane help-pane-dmarc">
<h2>DMARC record</h2>
<p><code>p=none</code> does not affect delivery. Tighten to <code>p=quarantine</code> then <code>p=reject</code> once reports look clean. The report address is set under Domain settings (or the Settings default).</p>
<p class="more muted"><label for="help-index">All topics</label></p>
</article>
<article class="help-pane help-pane-connection">
<h2>Connection settings</h2>
<p>Same host for every domain. Authenticate with an application login from this page. Auth is required on every port. The password is shown once at create or regenerate.</p>
<p>465 is implicit TLS; 587 is STARTTLS submission when that port is enabled.</p>
<p class="more muted"><label for="help-index">All topics</label></p>
</article>
<article class="help-pane help-pane-apps">
<h2>Applications</h2>
<p>SASL logins for this domain. Login is unique across domains; letters, digits, <code>.</code>, <code>-</code> and <code>_</code>. The password is shown once.</p>
<p>Address mode is which From addresses this application may use: any address of the domain, or a fixed list. A trusted-IP override gives those clients a higher ceiling than the domain (still ≤ level 1) and skips the domain check; everyone else uses the domain limit if set, otherwise level 1.</p>
<p class="more muted"><label for="help-index">All topics</label></p>
</article>
<article class="help-pane help-pane-domain-settings">
<h2>Domain settings</h2>
<p>Aggregate reports (<code>rua=</code>) inherit the Settings default, or you override them per domain. Level 2 is an optional ceiling for all senders on this domain; it must be ≤ level 1. Application overrides live on each application.</p>
<p class="more muted"><label for="help-index">All topics</label></p>
</article>
<article class="help-pane help-pane-export">
<h2>Export domain</h2>
<p>The file is a secret: it carries the DKIM key and application passwords, so published DNS does not have to change on the other instance. Transfer it securely, or encrypt it as <code>.spde</code>.</p>
<p class="more muted"><label for="help-index">All topics</label></p>
</article>
</aside>
<script>
(function () {
var q = new URLSearchParams(location.search);
if (q.get("view") === "phone") document.getElementById("vp-phone").checked = true;
if (q.get("role") === "domain") document.getElementById("role-domain").checked = true;
if (q.get("theme") === "dark") document.getElementById("theme-dark").checked = true;
if (q.get("inbound") === "0") document.getElementById("feat-inbound").checked = false;
function gate() {
if (!document.getElementById("role-domain").checked) return;
var h = location.hash.slice(1);
var blocked = {
status: 1, "mail-queue": 1, "system-log": 1, backup: 1, users: 1,
"user-form": 1, "user-delete": 1, inbound: 1, "inbound-domain": 1,
"inbound-backup": 1, "inbound-delete": 1, dmarc: 1
};
if (blocked[h] || !h) location.hash = "domains";
var brand = document.querySelector(".nav .brand");
if (brand) brand.setAttribute("href", "#domains");
}
gate();
document.getElementById("role-global").addEventListener("change", function () {
var brand = document.querySelector(".nav .brand");
if (brand) brand.setAttribute("href", "#status");
});
document.getElementById("role-domain").addEventListener("change", gate);
window.addEventListener("hashchange", gate);
document.querySelectorAll(".nav a").forEach(function (a) {
a.addEventListener("click", function () {
document.getElementById("nav-open").checked = false;
});
});
})();
</script>
</body>
</html>