Skip to content

Add METANET (Plesk) provider support #13

Description

@rtanglao
Image

Add METANET (Plesk) provider support

Add metanet-plesk as a remediation provider in records.json so both the CLI
(--provider metanet-plesk) and the web app emit METANET-Plesk-specific fix instructions
for the 13-record Thundermail set (1 MX, 5 SRV, 4 TXT, 3 DKIM CNAME).

Internal ticket: TBPRO 7194
(7194_METANET_PLESK).

METANET (metanet.ch, Swiss host) exposes DNS through the Plesk Administration Tool
("DNS-Einstellungen" → Ressourceneintrag zur Zone hinzufügen), in a German-language UI.
This is the first Plesk-based panel we support, and it is the most field-granular one so
far — the SRV form has seven inputs.

⚠️ Ship as UNVERIFIED first, per the godaddy/ionos/ovh/hover/digitalocean/porkbun
precedent: prefix each header string UNVERIFIED — until confirmed end-to-end, then drop
the prefix. The SRV field layout below is confirmed from a screenshot of the live
Ressourceneintrag bearbeiten form on thundermail.example.org; the TXT form is
confirmed from METANET's own docs; MX and CNAME field labels are not yet confirmed
(standard-Plesk guesses — see Open questions).

🎯 thundermail.example.org is the verification target. It is a METANET Plesk zone
delegated to ns1.hera.metanet.ch / ns2.hera.metanet.ch, so query it authoritatively:
uv run verify_thundermail_dns.py thundermail.example.org --resolver ns1.hera.metanet.ch
Live web preview: https://thunderbird.github.io/dns-scripts/?domain=thundermail.example.org
Current state: 8 passed, 5 failed — MX, all 4 TXT and all 3 DKIM CNAMEs are correct;
all 5 SRV records fail. See the blocker below.

🚨 Blocker: SRV weight 1 cannot be entered in Plesk

Plesk renders the SRV weight ("Relative Gewichtung für Einträge mit gleicher
Priorität
") as a dropdown with fixed steps of 5, not a free-text field. The options are:

sehr hoch (50), 45, 40, 35, 30, 25, 20, 15, 10, 5, niedrig (0)

Our record set specifies weight: 1 for all five SRV records, and value_templates.SRV
uses match_mode: "exact" — so a METANET/Plesk user literally cannot make the checker
pass.
This is confirmed live: the operator picked niedrig (0), and all five SRV records
now read 0 0 <port> mail.thundermail.com against an expected 0 1 <port> …:

FAIL _jmap._tcp        expected: 0 1 443 mail.thundermail.com
                            got: 0 0 443 mail.thundermail.com
FAIL _caldavs._tcp     expected: 0 1 443 mail.thundermail.com
                            got: 0 0 443 mail.thundermail.com
FAIL _carddavs._tcp    expected: 0 1 443 mail.thundermail.com
                            got: 0 0 443 mail.thundermail.com
FAIL _imaps._tcp       expected: 0 1 993 mail.thundermail.com
                            got: 0 0 993 thundermail.example.org   <- also a wrong target
FAIL _submission._tcp  expected: 0 1 587 mail.thundermail.com
                            got: 0 0 587 mail.thundermail.com

(The _imaps target is separately wrong — the Zielhost field ended up holding the zone apex
instead of mail.thundermail.com; the operator can fix that one. The weight mismatch on all
five is the structural problem.)

This needs a decision before the provider block is finalized, because whatever we emit
for the Weight field is either unselectable or knowingly non-matching:

  • Option A (recommended): stop comparing SRV weight. With a single target in the RRset
    the weight has no operational effect — relative weighting only matters between multiple
    targets at the same priority. Add a match mode (e.g. exact_ignore_weight) to
    value_templates.SRV, implemented in both interpreters + a parity fixture + linter case.
    This unblocks METANET/Plesk and any other panel with a constrained weight picker, and
    costs nothing in correctness.
  • Option B: change the canonical weight to 0 in records.json (Thundermail-side call —
    needs sign-off from whoever owns the published Thundermail setup instructions, and would
    flag every domain that currently has weight 1).
  • Option C: emit niedrig (0) and accept a permanent FAIL for METANET users. Rejected —
    it breaks the "exit 0 ⟺ everything correct" contract.

Option A is probably its own small issue that this one depends on; note the interaction
before writing the metanet-plesk block.

Add-record model

Plesk uses one add-record form with an Eintragstyp (entry type) dropdown — like
bunny/godaddy/hover/digitalocean/porkbun, not per-type sections like
cosmotown. Offered types: NS, A, AAAA, CNAME, MX, PTR, TXT, SRV, DS, CAA.

Path: Plesk → Websites & Domains → your domain → DNS-Einstellungen → Eintrag hinzufügen
→ pick Eintragstyp
, fill the fields, Übernehmen (Apply) — then click Aktualisieren
(Update) on the yellow banner to commit.

Key conventions:

  • Apex host = BLANK. The Domainname field is left empty; the panel prints the zone
    suffix (.thundermail.example.org.) immediately to the right of the input. So this is the
    blank-apex pattern of bunny/cosmotown/ovh/porkbun — use {subhost} for
    MX/TXT/CNAME (never @), and {srvsubhost} for the SRV Domainname (blank at the
    apex, the token that exists for hover).
  • SRV splits Service and Protocol into separate fields — WITHOUT the leading underscore.
    Both fields carry the hint "Beispiel: SIP (ohne Unterstrich)" ("Example: SIP, without
    underscore"), and the live screenshot shows imaps / tcp typed bare. Our existing
    {service} / {protocol} tokens yield _imaps / _tcp with the underscore
    (godaddy/ionos/hover want them that way), so METANET needs two new interpreter tokens
    — see below. This is the first provider that strips the underscore.
  • Priority and Weight are dropdowns, not text inputs. Priorität shows
    sehr hoch (0) for 0 (0 is selectable, so SRV priority is fine; MX priority 10 is in the
    list too — METANET's own default template ships MX 10 mail.<domain>.). Relative Gewichtung is the constrained one — see the blocker above.
  • Weight, Port and Target are three separate fields (Relative Gewichtung, Zielport,
    Zielhost) — so SRV uses the individual {weight} / {port} / {target} tokens like
    namecheap/hover, not the packed {value}.
  • Targets are stored verbatim — NO trailing dot needed. Confirmed live: four of the five
    SRV records hold exactly mail.thundermail.com. and the MX holds 10 mail.thundermail.com., i.e. Plesk appends the root dot itself and does not append the
    zone name. So emit plain {target} like bunny/porkbun, not {target}. like
    ovh/digitalocean.
  • TXT values are auto-quoted by the panel. METANET's docs are explicit: "The entry type
    TXT automatically sets the including quotation marks (example: "MS=ms14335100"). In this
    example, you would therefore only enter MS=ms14335100."
    So emit the unquoted {value}
    same as cosmotown. Field label is TXT-Eintrag.
  • Two-step save. New/changed entries are marked with an exclamation mark and are not
    live
    until Aktualisieren is clicked on the pending-changes banner; several edits can be
    batched. Worth calling out in the headers so users don't add all 13 records and conclude
    the tool is broken. Propagation can then take several hours.

New interpreter tokens

Add to the resolve_record / resolveRecord SRV-label split, in both Python and JS
(the sync rule), plus a tests/fixtures/parity_cases.json case:

Token Value for _imaps._tcp Why
{bareservice} imaps {service} minus the leading _ — Plesk's Service-Name
{bareprotocol} tcp {protocol} minus the leading _ — Plesk's Protokoll

(Name bikeshed welcome — {bareservice}/{bareprotocol} reads consistently with the
existing {service}/{protocol}/{srvhost}/{srvsubhost} family.) Computed for all
records like the rest of the split tokens, referenced only by the metanet-plesk SRV
template. Remember: the records.json linter asserts every template interpolates with no
unknown/leftover token, so the tokens must land before the provider block.

Field mapping

Type Fields (Plesk label → value)
MX Eintragstyp=MX, Domainname={subhost} (blank at apex), Mail-Exchange-Server={target}, Priorität={priority} (dropdown)
SRV Eintragstyp=SRV, Service-Name={bareservice} (imaps, no underscore), Protokoll={bareprotocol} (tcp, no underscore), Domainname={srvsubhost} (blank at apex), Priorität={priority} (dropdown, sehr hoch (0)), Relative Gewichtung=see blocker, Zielhost={target}, Zielport={port}
TXT Eintragstyp=TXT, Domainname={subhost} (blank at apex), TXT-Eintrag={value} (no quotes — the panel adds them)
CNAME Eintragstyp=CNAME, Domainname={subhost} (DKIM label), Kanonischer Name={target}

SPF / MTA-STS / TLSRPT / DMARC are all plain TXT; DKIM here is a CNAME.
Service-Name, Protokoll and Zielport are the SRV form's required (*) fields.

Work items

  • Resolve the SRV weight blocker (Option A recommended — a match_mode that ignores
    SRV weight, in records.json + both interpreters + tests). Possibly a separate issue
    this one depends on.
  • Add {bareservice} / {bareprotocol} to the SRV-label split in
    verify_thundermail_dns.py and app.js, plus a parity_cases.json fixture.
  • Add the metanet-plesk provider block to records.json (headers UNVERIFIED —
    prefixed): {subhost} apex, {srvsubhost} SRV apex, bare service/protocol, separate
    Weight/Port/Target, plain {target} (no trailing dot), unquoted TXT.
  • Decide the label language — German labels as shown in the panel, or German with an
    English gloss (see Open questions).
  • Mention the Aktualisieren (Update) commit step in the headers.
  • uv run verify_thundermail_dns.py example.com --provider metanet-plesk renders all 13
    records in both --fix-format table and long without error.
  • Web app "metanet-plesk" provider mirrors the CLI output (table + detailed).
  • uv run python -m unittest discover -s tests -t . -v green (linter + parity).
  • Update README.md provider list + RELEASE_NOTES.md and RELEASE_NOTES.html.
  • Update CLAUDE.md crucial-decisions with METANET/Plesk's quirks (blank apex,
    underscore-less Service/Protocol, dropdown Priority/Weight, verbatim targets,
    auto-quoted TXT, two-step save, German UI).
  • Verify against thundermail.example.org (--resolver ns1.hera.metanet.ch): confirm
    the MX and CNAME field labels on the live panel, fix the _imaps Zielhost, then drop
    the UNVERIFIED — prefixes.

Open questions

  • Provider key / label. metanet-plesk (as in the ticket) vs plain plesk — the form
    layout is stock Plesk and would serve any Plesk host, but the verbatim-target and
    auto-quoting behaviours were only confirmed on METANET's build. Recommendation: ship
    metanet-plesk now; generalise to plesk only if another Plesk host shows up.
  • Label language. Every existing provider uses the panel's own English labels. METANET's
    Plesk is German (Plesk can switch UI language). Emit the German labels verbatim
    (Zielhost), or German + gloss (Zielhost (Target host))? Recommendation: German
    verbatim, since that's what the user sees, with the English hint in the header text.
  • MX / CNAME field labels are unconfirmed. Mail-Exchange-Server and Kanonischer Name
    above are standard-Plesk German labels, not read off a live METANET form — the docs only
    screenshot the NS, TXT and SRV forms. Grab MX and CNAME add-record screenshots before
    dropping UNVERIFIED.
  • Subdomain zone. thundermail.example.org is a delegated subdomain zone (its own
    Plesk DNS zone, apex = thundermail.example.org). Nothing special for us — the tool just
    takes that as the domain — but note it when reading the screenshot's .thundermail.example.org.
    suffix.
  • Escape hatch. METANET sells a Premium Service: Individual DNS settings — email
    support@metanet.ch with the records in copyable form and they enter them. Worth a line in
    the headers (or an SRV-specific fallback, as cosmotown routes SRV to support) if the
    weight blocker isn't resolvable.

Reference docs

  • Plesk: DNS-Verwaltung (German, official) — https://www.metanet.ch/de/support/dns-nameserver/dns/plesk-dns-verwaltung
  • Local PDFs: THUNDERBIRD_2023/TBPRO/7194_METANET_PLESK/Plesk_ DNS-Verwaltung.pdf (German)
    and translated-to-english-by-firefoxl-pesk_ DNS-Verwaltung.pdf (Firefox translation).
  • Live SRV form screenshot (with the Weight dropdown open, showing the 0/5/10…50 steps):
    THUNDERBIRD_2023/TBPRO/7194_METANET_PLESK/SRV Einträge 2.jpg

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions