Skip to content

Mailing Lists

Christopher Holloway edited this page Oct 18, 2026 · 7 revisions

Mailing Lists

Written on 24 September 2026 from hmailserver/docs/MailingLists.md, DistributionLists.md and BounceSuppression.md, and brought up to 6.3.4 (27 September 2026, schema 6104), on 26 September 2026. The distribution list itself is in every release; the membership query (schema 6074) and the list manager (schema 6092) are new in 6.3.4. Brought up on 5 October 2026 to 6.3.6 (6 October 2026, schema 6108), for what 6.3.6 changes on this page. 6.3.7, the latest release (17 October 2026, schema 6108), is not yet reflected here.

A distribution list sends one message to its members - everyone@example.com, kept by an administrator (Aliases Lists and Public Folders §13.2). A mailing list is a distribution list people join and leave themselves, whose every member gets a copy of their own with a way out that works, which keeps an archive, offers a daily digest and notices when an address has died. That is what a small organisation runs Mailman for, and since 6.3.4 it is the same thing inside the mail server: a distribution list with the list manager switched on.

flowchart TD
    subgraph addrs["The addresses a managed list answers"]
        POST["team@ - post"]
        SUB["team-subscribe@ - join, and confirm"]
        UNSUB["team-unsubscribe@ - leave"]
        REQ["team-request@ - help"]
        OWN["team-owner@ - the person who runs it"]
        BNC["team-bounces@ - delivery reports"]
    end
    POST --> COPY["One copy per member,<br/>with List-* fields and<br/>the member's own way out"]
    COPY --> EACH["each message"]
    COPY --> DIG["one digest a day"]
    COPY --> NONE["nothing for now"]
    BNC --> COUNT["counted per member:<br/>1 hard or 5 soft in 30 days<br/>pauses the member"]
Loading

Contents

A list that defines itself

Schema 6074. Before a list is managed at all, it can carry a membership query: one short text saying who its members are, answered against the server's accounts at the moment a message is sent. Exchange calls the shape a dynamic distribution group.

Query Members
(empty) The addresses under Recipients and nothing else - every existing list
domain Every active account of the list's own domain
domain:other.example Every active account of another hosted domain
pattern:sales-*@example.com Every active account whose address matches the wildcard
everyone Every active account of every active domain

It is a union with the list's own recipients (the contractor elsewhere stays under Recipients), answers accounts only, never a disabled one, and is resolved once per SMTP transaction. A domain administrator may set domain or a pattern in their own domain; everyone and another domain are the server administrator's. When the accounts cannot be read the recipient is refused 451 4.3.2, so the sender retries rather than the list silently reaching only its hand-kept rows. The Control Panel's list dialog has a Membership query field, and the Deck's list editor shows Delivers to now, each address marked Kept by hand or From the query.

Switching the list manager on

Schema 6092. Nothing changes for a list until it is switched on: every list an upgrade finds is unmanaged and expands to its members exactly as before.

  1. On the list: Managed in the Control Panel's list dialog, the managed field in the Deck's list editor and on PUT /api/v1/lists/{address}, or DistributionList.Managed over COM. The manager's other fields sit beside it: who may join, archive and how long, digest and its hour, Reply-To, subject prefix, footer, VERP and owner.
  2. The links. The confirmation page, each member's own page with its one-click unsubscribe, the owner's approval page, the subscribe form and the archive are all served by the REST listener, which is off until RestApiPort is set. Make it reachable over HTTPS, then set list_base_url (ListBaseUrl, on the Deck's Settings page), such as https://lists.example.com. Empty, the default, offers no link at all: the copies carry the mailto addresses alone. Only an https:// base gives RFC 8058's one-click.
  3. DKIM on the list's domain. The copies are signed as that domain after the list's fields are in place, so the signature covers List-Unsubscribe and List-Unsubscribe-Post - which Gmail and Yahoo check before they show their unsubscribe button. With DKIM off for the domain the copies go unsigned and the providers do not offer it.

The owner is the list's owner, or its moderator, or the domain's catch-all, or postmaster@ the domain - one address, reached through team-owner@ so that strangers never see it. The texts the manager answers with are the server's translatable messages (LIST_CONFIRM, LIST_REFUSED, LIST_HELP, LIST_APPROVE, LIST_BOUNCED, LIST_LEAVE, LIST_UNSUBSCRIBED), edited on the Deck's Server messages page.

Joining and leaving

subscribe_policy decides who may join:

Policy Who may join
0 closed (the default) Nobody by themselves; the administrator adds members, and a request is answered with a refusal
1 confirm Anyone: the address asked for is sent a confirmation with a token, and a reply or the page's button makes it a member
2 moderated Anyone the owner lets in: after the address confirms, the owner approves on a page - signed in to the webmail as the owner's own account - or on the Deck. A reply by mail approves nothing, because an envelope sender is free text

A request is by mail to team-subscribe@ or on the page /lists/team@example.com/subscribe. The confirmation goes to the address being subscribed, so a request in somebody else's name writes to them once and subscribes nobody; requests are rate-limited per requester and per list, and the page answers the same sentence whether or not the address was already there.

Each member chooses a delivery - each message, one digest a day, or none (still a member, sent nothing: on holiday). A member leaves from their own page (the https link in List-Unsubscribe), by RFC 8058 one-click - what Gmail's and Yahoo's buttons send - or by writing to team-unsubscribe@, which answers with a link and a token and does nothing more until the member follows it: anybody who knew a member's address could otherwise take them off. A member with an account here sees the lists they belong to on the webmail's Subscriptions page. An account the membership query answers has no row of its own and is sent each message; its copies carry a link of its own, and leaving keeps a row saying it has left, which the query does not get past.

What a member's copy carries

The list makes one list copy from the posting - the subject prefix added once, Reply-To set to the list when reply_to is 1, the footer appended to the text and HTML parts (never to a signed or encrypted posting, which it would break), every one of the posting's own List-*, Precedence, X-hMailServer-List-Member, Return-Path and DKIM-Signature fields taken off (6.3.6; 6.3.4 and 6.3.5 stopped after 64 of a name and left a 65th in every member's copy) - and each member's copy then carries:

List-Id: <team.example.com>
List-Post: <mailto:team@example.com>              (NO for an announcement list)
List-Help: <mailto:team-request@example.com?subject=help>
List-Subscribe: <mailto:team-subscribe@example.com?subject=subscribe>, <https://lists.example.com/lists/team@example.com/subscribe>
List-Unsubscribe: <mailto:team-unsubscribe@example.com?subject=unsubscribe>, <https://lists.example.com/lists/unsubscribe?t=...>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
List-Owner: <mailto:team-owner@example.com>
List-Archive: <https://lists.example.com/lists/team@example.com/archive>
Precedence: list

The envelope sender of every copy is team-bounces@ - or with VERP the member's own form of it - so a failure comes back to the manager and never to whoever posted. A posting carrying the list's own X-Loop, a report with a null sender and a posting this server marked as spam are not distributed.

The digest and the archive

With the digest on, the list writes one digest a day at digest_hour (7 by default) to every member whose delivery is the digest: a table of contents, then each message as a part of a multipart/digest (RFC 2046). Nothing new, no digest. At most 100 messages and 10 MB; the rest goes in the next.

With the archive at 1 (members) or 2 (public), every distributed message is kept under the data directory and shown by month on /lists/team@example.com/archive - as text only, never as HTML, because a list takes postings from strangers. A members' archive answers a member's token or their webmail session. archive_days deletes older messages (0 keeps everything). The archive's rows are not in the backup; its files, under the data directory, travel with a backup that includes messages.

Bounces

A delivery report to team-bounces+jo=example.org@ is about jo@example.org; one to team-bounces@ is about the final recipients its RFC 3464 report names. A 5.x.x is a hard bounce, a 4.x.x a soft one; a delayed warning counts for nothing. Anybody can write to the bounce address, so a report is believed only when it carries the member's tag (X-hMailServer-List-Member, returned in the failed message's header); the worst a forger can do is pause a member, never unsubscribe one.

Setting Default What it means
list_hard_bounces_to_unsubscribe 1 Hard bounces that stop a member
list_soft_bounces_to_unsubscribe 5 Soft bounces within the window that stop a member
list_bounce_window_days 30 The window soft bounces are counted in

A member who reaches either count is set to none, with the reason, and the owner is told once. The owner's Reset bounces on the Deck, or the member turning their delivery back on, clears it. The list's counts are its own; separately, with the server's bounce processing on, a member whose server keeps refusing the list's domain goes on that domain's suppression list, which applies to all the domain's mail and is emptied by hand (Monitoring & troubleshooting → Suppression list).

What it does not do

  • It does not rewrite From for DMARC. A posting from outside, from a domain that publishes p=reject, reaches outside members with a From that fails DMARC when the list changed the message, and Gmail and Yahoo may refuse it. Mailman's answer - the list's address in place of the author's - is not built. Know this before managing a list that takes mail from such domains.
  • No posting from the web, no list configuration by its owner on a page of the list's own (the owner works through the Deck with a domain administrator's role), no list password, no approval by reply.
  • No NNTP, no IMAP access to an archive, and the archive is not searchable.
  • No weekly digest, and no plain RFC 1153 digest; the digest's table of contents and the list's own pages are in English (the messages it sends are the translatable server messages).
  • No bounce probe before a member is paused; a report that returns no header cannot carry the tag, so a member behind such a server is never paused.
  • One owner address per list.
  • A webhook route outranks the list. Since 6.3.4 an address, or a whole domain, can be routed to a URL (Mail In to a Webhook); a route is asked before the list, its -subscribe@, -request@ and other addresses included, and a route that only posts takes their mail instead of the list - posting rules and all. Route the addresses you mean rather than a list's whole domain.

The full account, with every field, route and COM member, is MailingLists.md and DistributionLists.md.

See also: Aliases Lists and Public Folders · Outbound Shaping · The Webmail · DNS for Your Domain

Home — full index

Start here

1. Install and run

2. Secure it

3. Operate it

4. Extend it

5. Contribute to it

Look it up — from any journey


About this wiki

Clone this wiki locally