Skip to content
hMailServer 6.3.8 is the current release, signed 2 November 2026. Windows and Linux, and an in-place upgrade from 5.3 or later, but for the original hMailServer's 5.7.Download 6.3.8 Documentation FAQ

Webmail, portal & Control Deck

6 Topics 6 Posts

The built-in webmail at /portal and browser administration at / — folders, compose, shared folders, calendars, CardDAV and CalDAV, passkeys and sessions.

  • The Control Deck: browser administration at /

    Pinned control-deck api
    1
    0 Votes
    1 Posts
    126 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) refuses a state-changing REST request that carries a credential - HTTP Basic included - when the browser reports it as cross-site (Sec-Fetch-Site, or an Origin that is not this server's); the Deck and the webmail act on their own origin, and scripts, hmctl and the PowerShell module send no such header, so none of them is affected. A credential confined to named domains, a domain administrator among them, is refused a domain's rename, a domain alias, a custom role's creation, change or delete, a domain's or an account's rule's bind_to_address, send_using_route or script_function action, its domain's relay host changed to an address that is not public and the server-wide metric history, over REST as over COM, and so from the Deck. 6.3.7 also carries the first step of the Control Deck's and the webmail's rebuild planned for 6.4 - the framework decision (ControlDeckFramework.md), the threat model of the web surface and the authorisation matrix with its check - with no product code. See the 6.3.7 announcement. The Control Deck is the administration page hMailServer serves at /, on the same listener as the API and the portal. On Linux it is the only administration front end there is, because that build has no Control Panel and no COM. On Windows it does not replace the Control Panel and it does less. Turning it on. One setting, RestApiPort, turns on the API, the Deck and the portal together. The default is 0, which leaves all three off, so a server upgraded without touching its settings serves none of them. The listener refuses to start rather than come up without a credential or without TLS, and logs which. Four messages begin RestApi: Refusing to start. The two you will meet are an unset administrator password and a non-loopback bind with no certificate. The TLS exemption applies only to a bind address that is exactly 127.0.0.1, localhost or ::1. 36.2 Signing in. The password goes once to POST /api/v1/session and is exchanged for an hmailsession cookie that is HttpOnly and SameSite=Strict. Two ceilings apply, 30 minutes idle and 12 hours absolute. Since 6.3.4 those are the defaults: SessionIdleSeconds and SessionLifetimeSeconds, server-wide or per domain, change them, and neither is set as shipped, after an upgrade or in a new database (DevicesAndSessions.md). Any cookie-authenticated request whose method is not GET or HEAD must carry X-Requested-With: hMailServer or it answers 403; HTTP Basic and bearer keys are exempt. Since 6.3.7 a state-changing request that carries any credential is refused when the browser reports it as cross-site (Sec-Fetch-Site, or an Origin that is not this server's); scripts, hmctl and the PowerShell module send no such header and are unaffected, as are the Deck and the webmail, which act on their own origin. An API key cannot mint a session. Since 6.3.4 an account that holds an administrative role signs in with its own address and password, and a passkey can start the same session, as can the organisation's single sign-on for the addresses named in oidc_administrators or saml_administrators (both empty as shipped). Sessions are process-local, so a service restart ends every one. 36.3 What it does. Dashboard, Domains, Delivery queue, DANE/TLSA, Settings, Rules, Routes, Certificates, Ports and Logs. 6.3.3 added full domain editing, the account editor in full, distribution lists and aliases, an IP-ranges view, fetch-account and backup views, the scripting, cache and indexing groups, and a CI harness of 292 checks. At 6.3.4 it has 40 views, in twenty languages besides English. The settings forms are worth understanding. The Deck fetches GET /api/v1/openapi.json once per session and draws each form from the schema of that group's PUT. Nothing in the page lists a settings key by name, so a key added to the API appears on its own, with its type, its permitted words, and a badge when it is read-only, write-only, required, or takes effect only on restart; since 6.3.4 a key kept in the settings store says instead whether a save applies it at once, after a reinitialise or after a restart. The Ports view carries a Restart the services now button, which posts to /api/v1/server/reinitialize. It drops connections in progress and ends your session with everyone else's. 36.4 What the Deck cannot write is measured, not asserted. build/check-deck-parity.py counts every field the desktop Control Panel writes against the REST API and the Deck, and reports in hmailserver/docs/DeckParity.md. Of 330 properties, 240 were writable over REST and 153 reachable from a Deck view when that work started; 328 and 322 at 6.3.3. At 6.3.4 the census counts 466, finds 458 of them writable over REST and every one of those reachable from a Deck view, and lists eight as missing: an account's directory fields (title, department, phone, mobile, office, note, photo and the opt-out). Those are written too, by PUT /api/v1/accounts/{address}/directory and the directory card of the Deck's account editor, under names the census does not match. 36.5 now opens with the 6.3.4 census and keeps its description of the 6.3.1 gaps, which were far wider, below it for the record; the 6.3.4 section of CHANGELOG.md is the current statement. Check DeckParity.md in the source tree before assuming a route does not exist.
  • The webmail portal at /portal

    Pinned webmail imap
    1
    0 Votes
    1 Posts
    122 Views
    ProgressiverobotP
    /portal is the self-service webmail hMailServer serves out of its REST listener. A mailbox owner signs in with their own address and password and gets folders, search, compose, drafts, flags and inline images. It has contacts too, and since 6.3.4 calendars and tasks and the organisation's directory. CardDAV (added in 6.3.3) and CalDAV (6.3.4), for other clients, are served on the web services HTTPS listener, not this one. It is compiled into the binary. There is no web root to deploy and no file to lose. At 6.3.4 the page is Portal.html and Portal.js, with its manifest, service worker, language catalogues and OpenPGP and S/MIME modules, all embedded at build time. So if the Control Deck answers and the portal does not, an absent file is not the cause. Check you are asking for /portal exactly, because the server answers the page at that path alone (what it serves below /portal/ since 6.3.4 are other pages: single sign-on, password reset, password-protected messages), and check no proxy is rewriting it. 36.14 has the rest of the symptom table. It fetches nothing externally. No font, no image, no stylesheet, no script from a CDN. Its Content-Security-Policy is default-src 'none'; script-src 'self'; style-src 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'; frame-src 'self' blob:; manifest-src 'self'; worker-src 'self'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'. Note img-src 'self' data: blob:, which permits no image from any other host; 'self' is there for images the server answers from its own routes, such as a colleague's directory photo. A sender's HTML is never merged into the page. It goes into an iframe sandboxed without allow-scripts and without allow-same-origin, under its own policy of img-src data:, so a remote tracking pixel stays in the markup and is blocked. Opening a message tells its sender nothing unless the reader chooses otherwise: showing remote content, for that message or always for that sender, serves the message under img-src data: https: http:, and the pixel then loads. 36.10 explains why the obvious design cannot work. It is not an administration interface. Everything the portal does with a mailbox is under /api/v1/me/, and those routes answer to an account's own credentials and to nothing else. The administrator password is refused on every one of them, and so is an API key. Administration is the other page, the Control Deck at /. Both sit on one listener behind one switch, RestApiPort, so exposing one exposes the other. If mailbox owners reach /portal from the internet, split the surfaces at a reverse proxy: the webmail public, /api/v1/session public because both pages sign in through it, and / plus the administration routes under /api/v1/ on the internal network. At 6.3.4 the webmail is more than /portal, /portal.js and /api/v1/me/*: it loads /portal-*.js, /portal.webmanifest, /portal-sw.js and /portal-lang/, and, for the features you switch on, people who are not signed in reach pages and routes such as /portal/*, /files/, /confidential/, /lists/, /held/, /recovery/verify, /api/v1/password-reset, /api/v1/passkeys/challenge and /api/v1/protected/. Check the proxy's list against what you use. 36.13 IMAP clients are unaffected. The portal is another client against the same store. A flag change requires the rights STORE requires, and every IMAP session on the folder is told. A move is IMAP MOVE: a copy with a new UID, then the original expunged. Folder create, rename and delete run IMAP's checks in IMAP's order and refuse with IMAP's own sentences. The new-mail probe reads the cached per-folder collection that IMAP, POP3 and delivery already share, so polling opens no message file. Outlook, Thunderbird, Apple Mail and phones carry on unchanged, and Roundcube pointed at the IMAP port remains a reasonable choice. Full reference: 36.6 The portal: what a user gets.
  • 0 Votes
    1 Posts
    147 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) accepts the UTF8 that classic Outlook for Windows (Outlook 2016 to 2024) sends after the POP3 login, at which its downloads had stopped; SMTP, POP3 and ManageSieve refuse a SASL PLAIN sign-in that asks to act as another account, and IMAP takes an authzid naming the client itself as none; IMAP's COPY and MOVE by message number read their set in the client's numbering and refuse one that names a message another session has just expunged (NO [EXPUNGEISSUED]), FETCH BODY[N.MIME] and BODY[N] of a multipart are the stored octets, and a refusal by the access control is NO [NOPERM]; and the IMAP and POP3 listeners can take the client's address from a PROXY protocol header, as the SMTP listener does, off by default. An authenticated submission is DKIM-signed as another hosted domain only when the account may send as the address its From names. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) signs a client in only when its user name is the account's stored address, compared in lower case after a domain alias and the default domain. A user name that differs from it in more than the case of the letters A to Z, or has white space at either end, may no longer sign in: such a name fails as a name that does not exist fails, so correct the user name in the client. See the 6.3.8 announcement. This topic covers connecting a mail client to hMailServer 6.3.4: ports, authentication, folders, automatic configuration, app passwords and devices. The one thing to know first: a new installation of 6.3.4 refuses a password from any machine but itself until the session is encrypted, and an upgraded server is not changed. On either, the TLS ports 465, 993 and 995 exist only once you create them. The settings your users need Setting Value Why, and what goes wrong Incoming server (IMAP) mail.yourcompany.com, port 993, SSL/TLS Implicit TLS: the handshake comes before the greeting. On a port not configured as SSL/TLS the client hangs, then times out Incoming server (POP3) mail.yourcompany.com, port 995, SSL/TLS The same. Only use POP3 if you have a reason to Outgoing server (SMTP) mail.yourcompany.com, port 587, STARTTLS Port 25 is for other servers, not for your users; many ISPs block outbound 25 from residential lines User name The full email address, [email protected] A bare name works only with Maintenance → Advanced → Default domain (for unqualified logons) set Password The account's password, or an app password if the account is enrolled in TOTP See the app passwords section below Encryption Never "none", never "auto-detect" "Auto-detect" is how a client ends up sending a password in the clear The ports, and what 6.3.4 changes on a new installation A new database is seeded with four rows - SMTP 25, SMTP 587, POP3 110 and IMAP 143 - and up to 6.3.3 all four were without encryption. Add 465, 993 and 995 yourself under Connections & protocols → TCP/IP ports in the Windows Control Panel, or on the Control Deck, once you have a certificate of your own. Connection security What the server does None (0) Plain text; STARTTLS is not advertised. Everything, including the password, is in the clear SSL/TLS (1) Handshake first, banner after. Correct for 465, 993 and 995 STARTTLS optional (2) Advertises STARTTLS and accepts both. The right setting for port 25 only: on a submission port it lets a misconfigured client send its password in the clear STARTTLS required (3) Answers 530 5.7.0 Must issue STARTTLS first. to every command except NOOP, EHLO, STARTTLS and QUIT. Right for 587, and for 143 and 110 if you keep them; on port 25 it silently refuses mail from every server that cannot do TLS With TLS chosen and no certificate selected the listener does not start at all: HM5113, Error initializing SSL. Certificate not set., in the application log, connection refused, nothing in the SMTP log; pick a certificate on the port. A port already in use is HM4316, and the rest of the server starts normally. A new installation of 6.3.4 has all four seeded listeners at STARTTLS optional with a self-signed certificate, which a mail client warns about until it is replaced, and the Internet IP range requires TLS for authentication; My computer (127.0.0.1) is left as it was. So from another machine, until the client sends STARTTLS: SMTP AUTH is not offered, and is refused with 530 A SSL/TLS-connection is required for authentication. if it is sent; IMAP advertises LOGINDISABLED, and LOGIN and AUTHENTICATE answer BAD A SSL/TLS-connection is required for authentication.; POP3 USER and AUTH answer -ERR. After STARTTLS every one of them works: the range's rule, not the port's, is what protects the password. When ACME's first certificate arrives it is given to every listener holding the self-signed one: see Let's Encrypt certificates are automatic. An upgraded or attached database is never changed. On Linux the defaults are applied at the unit's first start only when it comes within a day of --create-database. Which databases get them, the verb that applies them by hand and the checklist item that judges an upgraded server are in Hardening a public mail server. The IP range, not the port. RequireTLSForAuth lives on the IP range (Access & abuse protection → IP ranges, the Require auth tab). A range that requires TLS is offered no authentication mechanism at all on a cleartext connection, and IMAP additionally advertises LOGINDISABLED, so a client learns before it sends the password. DisableAUTHList is a comma-separated list of local ports on which SMTP AUTH is neither advertised nor accepted; use it to keep authentication off port 25. Since 6.3.4 such [Settings] keys are kept in the database: change them in the Control Panel, over REST (PUT /api/v1/settings/ini/{name}) or on Linux with hmailserver --set-setting NAME=VALUE. The database's value is the one used; where the service can write hMailServer.ini, a line edited there is put back (HM5804). Which port, decided once. For sending, 587 with STARTTLS required, or 465 if the client does not do STARTTLS properly; for receiving, 993, or 995 for POP3 on exactly one device. Never 143 or 110 without STARTTLS required, and deny 143 and 110 at the firewall once no client is left on them. What the server will actually accept Protocol Mechanism Advertised when Default on a fresh install SMTP AUTH LOGIN AUTH is enabled on the port Yes SMTP AUTH PLAIN Allow plain-text authentication is on No - authallowplaintext is seeded 0 SMTP AUTH SCRAM-SHA-256 Always, when AUTH is enabled Yes SMTP AUTH SCRAM-SHA-256-PLUS On a TLS connection Yes, on a TLS connection SMTP AUTH XOAUTH2 / OAUTHBEARER OAuth2 is enabled, and by default over TLS No SMTP AUTH EXTERNAL The handshake verified a client certificate on that port No IMAP LOGIN command Unless the port or the IP range requires TLS on a cleartext connection Yes IMAP AUTH=PLAIN, AUTH=SCRAM-SHA-256 Allow SASL PLAIN authentication is on No - EnableImapSASLPlain is seeded 0 IMAP SASL-IR Allow SASL initial client response is on No POP3 USER/PASS, SASL PLAIN, SASL SCRAM-SHA-256 Unless authentication is refused on this cleartext connection Yes The switches are on Connections & protocols → Protocols, the SMTP and IMAP tabs. Two consequences look like client bugs when they are not. On a default install, an IMAP client that insists on AUTHENTICATE PLAIN finds no AUTH= capability at all and must fall back to LOGIN. And SCRAM is always the account's own password, so an account with a second factor enrolled, or whose password has passed PasswordPolicyMaximumAgeDays, cannot sign in over SCRAM at all. Outlook on the desktop will not use SCRAM: it needs LOGIN or PLAIN. What the conversation looks like An extract of IMAP on a default install: * OK IMAPrev1 a002 LOGIN alice secret a002 NO Invalid user name or password. Please use full email address as user name. a003 LOGIN [email protected] secret a003 OK LOGIN completed The hint is given because the user name has no @. SMTP submission on 587, STARTTLS required: 220 MAIL01 ESMTP EHLO laptop.example.net 250-MAIL01 250-SIZE 20971520 250-8BITMIME 250-PIPELINING 250-CHUNKING 250-BINARYMIME 250-SMTPUTF8 250-ENHANCEDSTATUSCODES 250-DSN 250-STARTTLS 250 HELP MAIL FROM:<[email protected]> 530 5.7.0 Must issue STARTTLS first. STARTTLS 220 Ready to start TLS There is no AUTH line in that first answer, because the port requires TLS. After the handshake the client must send EHLO again, since the session starts afresh (RFC 3207 section 4.2). The second answer carries 250-AUTH LOGIN SCRAM-SHA-256 SCRAM-SHA-256-PLUS. Folders your users will look for A brand-new mailbox contains INBOX and nothing else. Drafts, Sent, Trash and Junk are created only when Create Drafts, Sent, Trash and Junk for new accounts is switched on (Connections & protocols → Protocols → IMAP), and the setting is seeded off. That is the usual explanation for "my sent mail is not on the server": the client fell back to a local folder because it found nothing designated \Sent. Fact Value Hierarchy delimiter ., so a subfolder of Work is Work.Invoices Public folders namespace #Public Other users' folders namespace #Users, only when ACL (shared folder permissions) is on - it is on by default Special-use designations \Drafts \Sent \Trash \Junk, advertised as SPECIAL-USE and CREATE-SPECIAL-USE IMAP or POP3. hMailServer advertises EXPIRE NEVER, so "leave mail on server" means for good, and a second POP3 session on the same mailbox gets -ERR [IN-USE]. What 6.3.4 changed in IMAP's answers Dovecot's imaptest was first run against this server on 25 September 2026: 31 of its 71 scripts failed, and 4 after 6.3.4 made the server answer as the RFCs say. The capability list is unchanged. A client that relied on the old answers may notice these: When the client 6.3.4 answers Names a mailbox that does not exist, or needs a right the account lacks NO, where it used to answer BAD; a failed SELECT or EXAMINE leaves no mailbox selected Sends an unknown LIST selection or return option BAD Fetches any BODY[<section>] without .PEEK Sets \Seen, BODY[HEADER] and BODY[1.MIME] included Appends a message Stores the literal byte for byte, where it used to add a CRLF, so RFC822.SIZE of a newly appended message is two octets smaller Pipelines a command behind an APPEND with a non-synchronising literal Answers it; it used to be thrown away, which stopped mbsync after one message Sorts, or threads Uses every criterion and builds the RFC's trees, so either can order messages differently Is the first session to be told of new messages Counts them as \Recent, where it said * 0 RECENT Automatic client configuration The server can configure clients, so users type only their address and password. AutoconfigEnabled is already 1; giving the web-services listener a port is what turns it on (WebServicesHttpPort=80, WebServicesHttpsPort=443), at the next reinitialise or restart, since the listener binds when it starts. Both port keys default to 0. Then point autoconfig.yourcompany.com (Thunderbird) and autodiscover.yourcompany.com (Outlook) at the server; both are ordinary host records. URL path Format Fetched by /mail/config-v1.1.xml, /.well-known/autoconfig/mail/config-v1.1.xml Thunderbird autoconfig XML Thunderbird and its relatives /autodiscover/autodiscover.xml Outlook POX autodiscover XML Outlook /email.mobileconfig, /mail/config.mobileconfig Apple configuration profile iOS and macOS Mail Which port ends up in the answer. The server reads your actual port rows and picks the best one per protocol: Rank Port configuration socketType written 100 SSL/TLS (implicit) SSL 90, 80 STARTTLS required on port 587; on any other port STARTTLS 70, 60 STARTTLS optional on port 587; on any other port STARTTLS 10 No encryption plain A protocol with no port at all is omitted. The host name is AutoconfigClientHost if set, otherwise the server's configured host name; with both empty the endpoint answers 404. Delegated mailboxes (since 6.3.4). The Outlook answer also names an AlternativeMailbox for every account that has opened a folder to the address asked about, but only to a request with HTTP Basic credentials, over HTTPS, for that very address. An Outlook IMAP profile never sees it: Outlook reads the element only for an Exchange account, and sends credentials only after a 401, which this server never returns here. What puts a delegated mailbox into a client's folder tree is the IMAP #Users namespace. None of this has been measured in Thunderbird or Outlook. Accounts with a second factor: app passwords A mail client has nowhere to type a six-digit code. Once a TOTP secret exists on an account, the account password is no longer a mailbox credential, and an app password is the only one. The order matters, and the Windows Control Panel puts the tabs in that order on purpose: Open the account: Accounts & domains → Domains → your domain → Accounts → the account. App passwords tab: create one per device, and write down the generated value; it is shown once. Two-factor tab: enrol the secret. Do it the other way round and every configured client stops working with no way back in. App passwords work for Active Directory accounts too, and are unaffected by password expiry, so enabling PasswordPolicyMaximumAgeDays does not silently break configured clients. A person makes their own on the webmail's Security page, which asks for the account's password again. Passkeys and single sign-on do not reach a mail client (since 6.3.4). They sign a person in to the webmail and the Control Deck and nothing else. An account that single sign-on or SCIM made has a random password nobody was told, so it needs a password set by an administrator before its person can make an app password for a phone. A recovery code is not a mail-client credential either. A mailbox encrypted at rest takes its own password only (since 6.3.4). It cannot enrol a TOTP second factor; on IMAP and POP3 its app passwords, an OAuth2 token, a client certificate and a Kerberos ticket are refused, and SCRAM does not open a key that is closed, so the client signs in with IMAP LOGIN or AUTHENTICATE PLAIN, or POP3 USER/PASS or AUTH PLAIN; and a client that was signed in when the switch was made is disconnected once. Devices: what revoking does Every client that has authenticated is one entry on the account's Devices list. Revoking a mail client's entry drops a live IMAP, POP3 or SMTP connection where it stands; a CalDAV, CardDAV or ManageSieve client has no connection to drop, so revoking one means revoking its app password. A revoke is not a ban: a client whose credential still works signs in again, so turn off its app password too, or change the account's password, which drops every connection of the account. Where the list is and what revoking does exactly are in Signing in. When it does not work What the user sees What the server said Cause Fix "Invalid user name or password", with a hint NO Invalid user name or password. Please use full email address as user name. Bare user name, no default domain Use the full address "Invalid user name or password", no hint NO Invalid user name or password. Wrong password, inactive account or domain, a TOTP-enrolled account given its account password, or, since 6.3.8, a user name that differs from the stored address in more than the case of the letters A to Z or has white space at either end Check both are active; issue an app password; give the address as stored "Authentication required" on sending 530 5.7.0 SMTP authentication is required. The client is not logged in for outgoing mail, or is on port 25 Tick "my server requires authentication"; move to 587 "Must issue STARTTLS" 530 5.7.0 Must issue STARTTLS first. Client encryption set to none on a STARTTLS-required port Set the client to STARTTLS A new 6.3.4 installation refuses the password from another machine 530 A SSL/TLS-connection is required for authentication. The Internet range requires TLS for authentication Set the client to STARTTLS, or use 465, 993, 995 once created Client hangs on connect, then times out Nothing: no session is created Implicit TLS expected on a STARTTLS port, or the reverse Match the port's connection security Repeated logon failures, then silence NO Too many invalid logon attempts. and a BYE Ten failures on one connection, the per-name lockout, or the per-IP auto-ban Access & abuse protection → Auto-ban Large attachment rejected 552 5.3.4 Message size exceeds fixed maximum message size The maximum message size, default 20480 KB Max message size, or the domain's own limit While diagnosing on Windows, look at Monitoring & troubleshooting → Live logs and Server status, which lists the listeners the service actually brought up. Per client: if Thunderbird offers "no encryption" it found a plain port, so fix the port, not the client. Where to read more Wiki: Connecting a Mail Client, Ports Reference, Encryption and Certificates, Sign-in and Identity. Documents: SecureDefaults.md, DevicesAndSessions.md. The public documentation: 7. Connecting a mail client, 7.2 Automatic client configuration, 22. Ports reference. On this forum: Let's Encrypt certificates are automatic, IMAP and POP3 on the server for shared mailboxes, when a saved setting takes effect, First install on Linux, Upgrading to 6.3.4 and the 6.3.4 announcement.
  • 0 Votes
    1 Posts
    132 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) signs an authenticated SMTP submission as another domain hosted here only when its account may send as the address its From names - the list the From picker below draws on: its own address, an alias pointing at it, a mailbox that granted it the post right, an address it proved or a masked address of its own; otherwise the mail is still accepted and sent, unsigned as that domain. The webmail's own send already holds From to the account, so mail sent from the page is signed as it was. 6.3.7 also carries the first step of the Control Deck's and the webmail's rebuild planned for 6.4 - the framework decision, the threat model of the web surface and the authorisation matrix with its check - with no product code. See the 6.3.7 announcement. This topic goes deeper into the webmail that hMailServer serves at /portal on the REST listener; what the page is and how it is turned on is in The webmail portal at /portal. The one thing to know first: most of what follows is the reader's own choice, bounded by a setting the administrator holds. Search with operators, the keyboard map, the rich-text editor, the From picker over aliases and granted mailboxes, plain signatures and the offline inbox listing were in 6.3.3; the three stars, the importance marker and its keys, and the rest of what follows are since 6.3.4. What the administrator bounds Each is a setting on PUT /api/v1/settings and the Control Deck's Settings view; since 6.3.4 many can also be set per domain or per account. Setting What it bounds Default recover_deleted_days How long deleted mail can be put back 0, off; at most 365 reader_offline_days_max The most days a reader may keep offline 30; at most 365; 0 offers none reader_masked_addresses_max Masked addresses per person 20; at most 200; 0 takes the page away reader_sender_addresses_max Send-as addresses per person, proved or not 5; at most 20; 0 takes the section away brand_logos_enabled Brand logos at all On; needs AuthenticationResultsEnabled and DMARC confidential_base_url The address the webmail is reached at from outside, which links are written against; falls back to quarantine_digest_base_url, then import_oauth_redirect_base_url Empty Search operators and keyboard shortcuts The search box searches the folder shown; the options panel beside it builds the same syntax from fields and can search every folder the account may read. Operator What it matches word, "quoted phrase" The word in the subject or the sender, failing those anywhere in the message; the words together, in that order -term, a OR b What follows the minus must not match; one of the two must match. OR in capitals - or is a word from:, to:, cc:, bcc:, subject: The sender's address or name; a recipient's, in To or Cc; an address or name in Cc, or in Bcc (only in the sender's own copy); the subject has:attachment, has:link, filename: An attachment; an http or https link; an attachment whose file name contains the text larger:, smaller: By size, as in larger:10M before:YYYY-MM-DD, after:YYYY-MM-DD By date older_than:, newer_than: By age: 7d, 2w, 3m, 1y in:folder Messages in that folder; a whole path may be given is:unread, is:read, is:flagged, is:unflagged, is:answered, is:muted, is:pinned By the flag or keyword label:name An IMAP keyword; label:$Important is the sidebar's Important view category: The inbox tab: primary, social, promotions, updates or forums in_reply_to: Messages whose In-Reply-To or References names the message-id Since 6.3.4 a search request opens at most 2,000 message files and stops after about two and a half seconds, saying where to go on from; Search older messages carries on. The keyboard map (? lists it) acts on the message list and an open message, and nothing is captured while the cursor is in a text field. j and k move, Enter or o opens, u or Esc goes back, x ticks a row, e archives, ! files as junk (or, in Junk, as not junk), # deletes to Trash, l labels, m mutes, c writes, r a f reply, reply to all and forward, 1 to 9 run a quick step, / searches and Ctrl+K opens the palette. s gives the next of three stars; the second and third are the $Star2 and $Star3 keywords on top of the IMAP flag, so another client sees a flagged message. Offline mail The webmail has always kept the inbox listing and the last opened messages - thirty-one of them according to OfflineMail.md and EvidencePack.md, thirty according to README.md and the wiki - and an outbox of sends that failed for want of a connection. Since 6.3.4 it can also keep the text, formatting and attachments of the last days in the browser's own storage, and nothing is kept until the reader chooses. Settings, Offline in this browser, offers Nothing beyond the inbox listing and then the last 7, 14, 30, 60, 90, 180 or 365 days, up to the server's ceiling. The choice is the account's offline_days preference, so it follows the person between browsers; what is kept does not. How it fills. The store is IndexedDB, database hmPortalOffline. A message goes in when it is opened and the window covers it, and in the background, newest first. An attachment above 8 MB is not kept, and a message's parts stop at 32 MB together. When the window is bigger than the browser allows the site, the card says so and the newest are kept. Without a connection a message opens from the store, a folder lists what the store holds, and a search runs over the kept words with the operators the store can answer. has:link and in_reply_to: let a message through rather than hide it, OR is ignored, and a date operator is measured on the browser's clock. What it is not. It is not a backup and it is not synchronised back: a flag set offline is not sent when the connection returns. It is not encrypted beyond what the browser does with its own storage, so anyone who can open this browser profile can read what is kept. It does not survive sign-out: the database is deleted. Clear what is kept deletes everything now and sets the choice to nothing. Important, and sections of the inbox Gmail's importance marker, decided in the browser: no model is trained anywhere, and nothing about whom this person reads or writes to is sent to the server to decide it. The page counts in localStorage and forgets the whole of it at sign-out. What it decides is one IMAP keyword: $Important on a message judged important, $NotImportant on one the reader says is not, so Thunderbird, a phone and hmctl all see them. Each inbox message is judged once, the first time this session lists it; two points make it important. Reason Points You marked mail from this sender important, or not important +4, or -4 You write to this sender +3 You answered this sender +2 You open mail from this sender (two or more opened here) +1 You starred mail from this sender +1 It was sent to you by name (a To of five or fewer) +1 It is in one of the other tabs: Social, Promotions, Updates or Forums -2 A message that already carries either keyword is never judged again. The marker is changed from the arrow on the row, with an Undo, or with the keys + or = and -. Sent and Drafts have no marker. In Settings, Importance marker offers the page deciding (the default), marking by hand only, or no marker; Inbox sections can draw Important, Starred, Everything else (off by default). It will be wrong, which is why every mark can be undone in one press. It does not sort, hide or delay anything, and what it has learned stays in this browser; the keywords on the mail travel. A message that expires Gmail's confidential mode: the message is kept on this server, and what crosses the internet is a short notice with a link. It is not end-to-end encryption. A message this server can delete on a timer is a message this server can read, and anyone who administers the server, or a backup of it, can read a kept message while it exists. Nor does it stop a recipient photographing the screen. Sending. On the compose form, Expires (never, in an hour, a day, a week, a month) and an optional Passcode. Over the API, expires_in (seconds, 1 to 7,776,000 - ninety days) and passcode (4 to 64 characters) on POST /api/v1/me/messages. The send is refused when the server has no public address to write the link against (403, confidential_unavailable), and for a message the page signed or encrypted. The kept copy is a file under <data directory>/Confidential/<account id>/ with a row in hm_confidential (schema 6069); it counts against the mailbox's quota (413, quota_exceeded), and an account has at most 1,000 outstanding at once (400, confidential_ceiling). Each recipient is sent the notice; the sender's Sent folder gets the full message. Reading. The page at /confidential/<token> takes no credentials. The reader proves they are a recipient in one of three ways: signed in to this server's webmail as a recipient address; a six-digit code mailed to a recipient address, good for ten minutes and five tries; or the passcode, where ten wrong answers pause it for a quarter of an hour. After thirty wrong answers in all, codes stop opening the message. The page answers the same way whether or not the address typed is a recipient's. It shows the text, names other attachments without offering them, and runs no script. After the expiry or a withdrawal it answers 410. The sender's side. Messages that expire, on the Sent folder's menu, lists what was sent and whether and by whom it was opened, with a Withdraw button. The kept file is deleted when the expiry passes. The copies are not in the backup, but an archive taken by a 6.3.4 build made before the release holds them, in the clear: treat it as the mail itself, or delete it. Send-as and masked addresses The webmail's From picker offers four kinds of address: Kind Given by The account's own address Always An alias that points at it The administrator Another mailbox on this server That mailbox's owner, with the post (p) right on their INBOX An address at another provider The person, proved with a code mailed to it With SmtpAuthenticatedSenderCheck on, an authenticated SMTP session's MAIL FROM is held to the same list: see who may send as whom. Since 6.3.7 the DKIM signature follows the list too: an authenticated submission whose From names another domain hosted here is signed as that domain only when the account may send as that address, and is otherwise accepted and sent unsigned as that domain; the webmail's own send already holds From to the account. Proving an address elsewhere (since 6.3.4). Under Settings, Other accounts, Send mail as the person types the address and presses Send the code. The server mails a six-digit code to it from the postmaster of the account's domain, and the person types it back. The code lives in the server's memory for an hour and is never stored; five wrong tries end it. An address of a domain on this server is refused. What this cannot change: mail this server sends as [email protected] is not signed by Gmail's DKIM key and not sent from an address Gmail's SPF record names, so a receiver that honours the domain's DMARC policy will quarantine or refuse it when that policy says so. The page says so beside an address whose domain publishes quarantine or reject. For such an address, send from its own provider. Masked addresses. Under Settings, Masked addresses the person says what the address is for and presses Make an address; the server makes one in the account's own domain, such as [email protected]. While it is on, mail to it is delivered to the account, it is in the From picker (kind: masked), and a reply to mail that came through it goes out from it. Switched off, mail to it is refused at RCPT exactly as mail to an address that never existed is - not handed to the domain's catch-all. An account, alias or list made later with the same address takes it over. Both kinds of address are carried by the backup with the account. Recovering deleted mail It is off by default: recover_deleted_days (0 to 365) turns it on for the whole server, and a domain or an account may keep its copies for fewer days than the level above, never for more. What is kept is a message a person deletes: IMAP EXPUNGE, UID EXPUNGE and CLOSE on a folder with messages marked \Deleted; POP3 DELE; the webmail's delete and its emptying of a folder; and the retention the person chose, or the administrator's retention policy. What is removed for any other reason is not kept: an IMAP MOVE, a scheduled draft that has been sent, an account being deleted. Putting it back. Recover deleted items, on the Trash folder's menu and in the command palette, lists what was deleted within the window. Put back returns a message to its folder with its flags - or to a folder of the same path, or otherwise the INBOX - as a new message with a new UID. Delete for good removes it now. The copies are files under <data directory>/Recoverable/<account id>/; they count against no quota and are not in the backup. The retention task, six-hourly, ends the copies older than the window. Signatures and formatted messages A logo in the signature. Add image in the signature's toolbar (Settings > Name and signature) takes a PNG, JPEG, GIF or WebP file, judged by its first bytes. The whole formatted signature is at most 64 KB - 64,000 bytes, its images included, which leaves room for a logo of up to about 45 KB. A file that does not fit is drawn smaller, starting at 600 by 200 pixels. The limit is the account's signature through the webmail: the Control Panel, COM and the administrator's REST route do not check it. What the recipient receives. Gmail and Outlook do not display a data: image, so the server moves each one into a part of its own with a Content-ID and points the HTML at it with a cid: URL - multipart/related inside the alternative. At most 64 different images in one message are moved. Not every mail client displays WebP; a logo that has to show everywhere is safer as a PNG. The formatted editor rebuilds what is written from an allowed subset of HTML and drops everything else while keeping the words: a style, a colspan, a <script>, a remote <img src="https://...">. Since 6.3.4 it takes three more things: Emoji: a picker of about a thousand characters in eight groups, kept in the page itself. The names are English; there are no skin tones and no flags. Pictures, pasted, dropped or chosen: PNG, JPEG, GIF and WebP by their first bytes, with SVG refused; drawn down to 1,200 pixels on the longer side; at most 64 in a message. Tables, up to 50 by 20, written with border="1" cellpadding="6" cellspacing="0" as attributes, because Outlook renders mail with Word's engine and ignores most of a stylesheet. Tab moves to the next cell and, in the last cell, adds a row. There are no merged cells, no column widths or colours of the reader's choosing, and no resizing a picture once it is in. A second account in the same page Two mailboxes side by side: the account the person signed in as, and one more, added under Settings, Other accounts with its address and password. One press switches the whole page to the other. Nothing is collected or copied; this is not the Other accounts feature beside it, which collects mail from another mailbox into this one. The second account's session lives in the page, not in the browser. A cookie is one per origin, so its session is a token the page holds in memory (POST /api/v1/session?bearer=1), written nowhere - so a reload forgets it, and the person adds the account again. Everything the browser keeps between visits stays the first account's: the offline store, the outbox, push, the recent searches and the Important tally. What is thinner. One second account, not several. Remote images are not loaded in its messages, its S/MIME keys are unlocked by hand, and the one-session-per-device policy counts it. The form is never minted for the administrator (403), and a bearer session is the mailbox and nothing more: the server drops any administrative role from every request made on one. Brand logos The list shows a sender's BIMI logo as its avatar only when the claim is checked. Three things have to be on: AuthenticationResultsEnabled=1, the DMARC check, and BrandLogosEnabled (on by default). GET /api/v1/me/brand-logo?domain= answers the SVG only when all of these hold: default._bimi.<domain>, or the same name at its organizational domain, is a v=BIMI1 record with an l= location. The domain's DMARC policy is quarantine or reject with pct=100. A domain at p=none gets no logo, even for mail that passed. The location is https, reached only at a public address, within ten seconds and 32 KB, with no redirect followed. The file is SVG Tiny Portable/Secure. Every answer is kept a day per domain. A refusal is 404 with an outcome - no_record, declined, weak_policy, not_https, fetch_failed, not_tiny_ps or switched_off - and the webmail shows the initials. A Verified Mark Certificate (a=) is not validated. What it does not do No ActiveSync and no Exchange Web Services: a phone uses IMAP and SMTP, and CalDAV and CardDAV. Recall reaches this server's own mailboxes only, and only unread copies. A folder of a hundred thousand messages is paged, searched and selected whole without the page copying it: built, and not yet measured. Where to read more Wiki: The Webmail. Documents: WebmailShortcuts.md, OfflineMail.md, ImportantMail.md, ConfidentialMessages.md, SendAs.md, MaskedAddresses.md, RecoverDeleted.md, Signatures.md, FormattedMessages.md, SecondAccount.md, BrandLogos.md. The public documentation: 36.6 The portal: what a user gets. On this forum: The webmail portal at /portal, The Control Deck, Signing in for passkeys and account recovery, and the 6.3.4 announcement.
  • 0 Votes
    1 Posts
    138 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) refuses a state-changing REST request that carries a credential - HTTP Basic included, which a browser keeps once it has answered the sign-in box - when the browser reports it as cross-site (Sec-Fetch-Site, or an Origin that is not this server's), and holds the password-reset request to the same rule, so another site cannot have a current browser drive it; the webmail and the Control Deck act on their own origin, and scripts, hmctl and the PowerShell module send no such header, so none of them is affected. SMTP, POP3 and ManageSieve now refuse a SASL PLAIN sign-in that asks to act as another account. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) signs a name in to an account only when it is the account's stored address, compared in lower case after a domain alias and the default domain, on every protocol, the webmail, the REST API and COM, and for an OpenID Connect or SAML sign-in, so that the per-name lockout and the compromised-account hold can no longer be got round with another spelling of the name: a client that signs in with another spelling, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in, since such a name fails as a name that does not exist fails. A failed account sign-in at the REST listener through a reverse proxy on the same machine, like a failed CalDAV, CardDAV or Autodiscover sign-in from loopback or an address in WebServicesTrustedProxies, is counted against the name alone (AccountLockoutThreshold, off by default), never towards a ban of the proxy's address. See the 6.3.8 announcement. This topic covers how people prove who they are to hMailServer 6.3.4: what 6.3.3 already had, and what 6.3.4 adds. The one thing to know first is that there are two front doors. The mail protocols - IMAP, POP3, SMTP submission, CalDAV and CardDAV - take a password: the account's own, an app password, or an OAuth2 bearer token where that is switched on. The two pages - the webmail at /portal and the Control Deck at / - take the password as well and, since 6.3.4, a passkey or a sign-in at the organisation's identity provider. Operators upgrading should read the note on passwords with characters above U+00FF first. What 6.3.3 already had Feature What it is A second factor for an account A TOTP secret on the mailbox. Once one exists, the account's password stops being a credential wherever a code cannot be presented alongside it - every mail client, and the webmail's sign-in - and app passwords are what those use App passwords One per device, shown once, revocable one at a time; they work for directory-linked accounts too and are untouched by password expiry A second factor for the administrator Enforced by the server on every surface - COM, REST, the Control Panel The password policy Length, mixed case, digits, symbols, a common-password list, history and maximum age LDAP and Active Directory Passwords checked against the directory; provisioning from it on a preview-then-apply OAuth2 bearer tokens on the protocols SASL XOAUTH2 and OAUTHBEARER validated against an identity provider's published keys There is no self-service enrolment of an authenticator app in the webmail: an administrator sets and clears the second factor, over COM and the Windows Control Panel. Create the app passwords first and enrol the secret second, or every configured client stops working with no way back in. The breach check (since 6.3.4). A password being chosen, never at logon, can be refused because it is in the published corpus of breached passwords; the check is off by default, because it sends five characters of the password's SHA-1 hash off the server, and its settings are in Hardening a public mail server. Four more changes in 6.3.4. The administrator password, which the Windows installer wrote as an unsalted MD5 digest, is written again as PBKDF2 at the first successful administrator sign-in (a file the service cannot write keeps the old value and says so once, HM7494). An account's TOTP secret is sealed at rest once the installation has a stored-secrets key. A mailbox encrypted at rest takes its own password only: no TOTP secret can be enrolled on it, and its app passwords are refused. And a room or a piece of equipment cannot be signed into by any credential. Upgrading to 6.3.4: passwords with characters above U+00FF Before 6.3.4 every password hash was made over a conversion that gave NUL bytes on Windows from the first character above U+00FF on, and ? for each such character on Linux, so a password of Cyrillic, Greek or CJK letters could be opened by any other password of its length. Hashes are now made over UTF-8, which leaves every ASCII password's hash as it was. The consequence, item 24 of the CHANGELOG's upgrade list: a password hash stored before 6.3.4 of a password with a character above U+00FF is refused, to its own password too, and the application log names the account. An administrator sets such a password again, and soon: until then SCRAM-SHA-256, whose client derives its key itself, still opens such a PBKDF2 hash to the NUL bytes (Windows) or question marks (Linux) it was made over, which the server cannot tell from the password; and on Linux such a hash also still opens to the password written with ? for those characters. Every ASCII and Latin-1 password signs in as before. The same release fixed SCRAM-SHA-256 having accepted the account's own password for an account with a second factor enrolled, and a password past PasswordPolicyMaximumAgeDays; each is now a forced failure of the exchange. SCRAM is always the account's own password, since an app password's hash is not one SCRAM can derive from, so such an account cannot sign in over SCRAM at all. Passkeys A passkey (schema 6077) is a WebAuthn credential held by the person's own device, such as a phone or a security key. The private half never leaves it; the server keeps the public half. Passkeys are offered on a stock installation, and nobody has one until they enrol it. Enrolling. The webmail's Security page has a Passkeys card, and the Control Deck has a Passkeys view for the administrator's own. Both ask for the owner's password again, with the one-time code in X-hMailServer-OTP where a second factor is enrolled, or for an account one of its recovery codes in X-hMailServer-Recovery-Code; a request that was itself authenticated with an app password is refused outright. At most ten per owner. POST /api/v1/passkeys/options what the browser needs to make one POST /api/v1/passkeys the credential it made GET /api/v1/passkeys the ones this credential holds DELETE /api/v1/passkeys/{id} remove one Listing and removing are answered while passkeys_enabled is off as well. Signing in. POST /api/v1/passkeys/challenge hands out thirty-two random bytes, kept in memory for five minutes and spent on first use; the assertion goes to POST /api/v1/session, the one place this server mints a browser session, so the sign-in gets the same ceilings and a device-list entry marked passkey. Nobody types an address first. A passkey stands in for the one-time code: user verification - a PIN, a fingerprint or a face - is required at registration and on every assertion. It carries nobody past an inactive account or domain, the compromised-account hold or the domain's country policy. Behind several names. By default the relying party id is the Host header the page was served on. passkey_relying_party set to a registrable parent (example.com) lets a passkey made at mail.example.com work at webmail.example.com. With it set, passkey_allowed_origins must be set too, comma separated as the browser writes them (https://mail.example.com, https://webmail.example.com); until it is, no passkey is registered or taken. Not verified: the attestation statement, so an installation cannot say "only hardware keys of this make". Not in the backup: enrol the device again after a restore elsewhere. Registering and removing a passkey is on the audit trail. Single sign-on Since 6.3.4 a person can sign in at the organisation's identity provider instead, by OpenID Connect or by SAML 2.0 (for a provider such as ADFS that speaks only SAML). Once it is set up, both sign-in pages draw a button: Sign in with and the issuer's host name, or Sign in with SAML. It opens the webmail and the Control Deck and nothing else: IMAP, POP3 and SMTP still take the password or an app password. An identity provider that says somebody is an administrator is not obeyed; the Deck opens as the administrator only for an address listed in oidc_administrators or saml_administrators. There is no logout in either direction, so a person disabled at the provider keeps a session already started here until its own ceiling ends it, which is what the session policies below are for. The settings, the Entra ID, Okta and ADFS entries and the flows are in SCIM provisioning and single sign-on. Account recovery Three independent parts (schema 6076): a recovery address and ten one-time codes, which the person sets on the webmail's Security page, and the forgotten-password flow on the sign-in card. Setting either asks for the account's own password again - never an app password - and the second factor where there is one. What it needs. account_recovery_enabled is on by default and inert on a stock installation: links are written against the server's public base URL - confidential_base_url, falling back to quarantine_digest_base_url and then the import sign-in's import_oauth_redirect_base_url - and with none set POST /api/v1/password-reset answers 503. recovery_reset_link_minutes (30; 5 to 1440) is how long a reset link lives, and recovery_verify_link_hours (24; 1 to 168) how long the confirmation link lives. The recovery address counts for nothing until the link mailed to it is followed, on a page with a button rather than on the GET, which a mail scanner would follow first. It is never answered in full again, to anybody: [email protected] reads as [email protected]. The confirmation is metered at one a minute and six an hour for the address, and again for the asking account, answering 429 with retry_after. The ten one-time codes are made together, shown once and stored hashed. A code goes in X-hMailServer-Recovery-Code wherever an authenticator's code is asked for: POST /api/v1/session, POST /api/v1/me/password, POST /api/v1/me/app-passwords, PUT /api/v1/me/recovery and POST /api/v1/me/recovery/codes. The password is verified before the code is looked at, and the code is spent only once the whole attempt has been accepted. Every owner has a budget that needs no setting: five wrong codes in fifteen minutes, and for the next fifteen every code for that owner is refused - the right one too - with 429 and code second_factor_locked. And a code is taken once (RFC 6238, section 5.2): an accepted TOTP code is refused if it comes again within its window, without being counted as wrong. A script that sent the administrator's password and a code with every request signs in once instead, or uses an API key. Forgotten your password? on the sign-in card posts the address to POST /api/v1/password-reset, which takes no credentials and answers always the same - 202 and one sentence, in the same time - whether or not the address is a mailbox with a confirmed recovery address. Since 6.3.7 a request a browser reports as cross-site (Sec-Fetch-Site, or an Origin that is not this server's) is refused, so another site cannot have a current browser drive it. The limits are one request a minute and six an hour per address, and twenty an hour per client address; a loopback peer, such as a reverse proxy on the same machine, is held to thirty in any sixty seconds instead. The link goes to the confirmed recovery address and nowhere else, is good once, and dies after recovery_reset_link_minutes. Completing it applies the password policy, the reuse history and the breach check, ends every browser session of the account, tells the mailbox and the recovery address, and raises the account.password-reset alert. A used, expired or unknown token answers 410. The administrator can see whether an address is set and confirmed, its masked spelling and how many codes are unused - over REST, on the Deck's Recovery card and the Windows Control Panel's Recovery tab - and can clear either: DELETE /api/v1/accounts/{address}/recovery and DELETE /api/v1/accounts/{address}/recovery/codes. The administrator cannot read the address in full, read a code, set an address on somebody's behalf or generate codes for them. A recovery address is exactly as strong as the mailbox that receives it: a convenience, not a second factor, and an installation that cannot accept that sets account_recovery_enabled to 0. Recovery does not enrol a new authenticator either. Nor does a reset link open encrypted mail: for a mailbox encrypted at rest the link is refused before it is spent and the person is sent to their administrator, and for a zero-access mailbox the password it sets does not open the key, so at the next sign-in the page asks for the previous password or the recovery key. Devices and sessions Every client that has authenticated to an account is one entry of a list: the protocol (imap, pop3, smtp, web, dav, autodiscover or managesieve), the address it came from, the user agent where the protocol carries one, the credential it used, the app password's name where one was used, and when it was first and last seen. IMAP's ID command is deliberately not used, so two mail programs on one machine over IMAP are one entry. At most fifty entries are kept per account. Surface Read Revoke Webmail Settings → Security, the Devices card Revoke beside the entry; the box under the table adds the app password REST, the account GET /api/v1/me/devices DELETE /api/v1/me/devices/{id}, with ?credential=revoke and ?forget=true REST, an administrator GET /api/v1/accounts/{address}/devices DELETE /api/v1/accounts/{address}/devices/{id} COM (Windows) Account.GetDevices() Account.RevokeDevice(id, revokeCredential) Control Deck The Devices view Revoke, with the app password box hmctl hmctl device [email protected] list hmctl device [email protected] revoke ID [--credential] [--forget] PowerShell Get-HmDevice -Address [email protected] Revoke-HmDevice -Address [email protected] -DeviceId ID [-Credential] [-Forget] What revoking does, exactly. A browser session's token is invalidated, so the next request on that cookie is 401; a live IMAP, POP3 or SMTP connection is dropped where it stands; and with credential=revoke the app password is deactivated - not deleted, so the person can put it back if the phone turns up. CalDAV, CardDAV and ManageSieve clients have no connection to drop, so revoking one means revoking its app password. A revoke is not a ban: a client with a credential that still works signs in again and clears the mark. The account's own revoke is refused (403) to a request that came in on an app password, and an administrator's revoke is recorded in the audit trail. The list, hm_signins, is not in the backup. The session policies govern every signed-in browser - the webmail, the portal and the Deck - server-wide and per domain, and are re-read at every request. A mail program's connection is not a session in this sense. Policy Setting Unset means Idle timeout session_idle_seconds 1800 seconds; at most 2592000 (thirty days) Absolute lifetime session_lifetime_seconds 43200 seconds, twelve hours; at most 2592000 One session per device session_one_per_device (a domain's: 0 the server's answer, 1 on, 2 off) Off Bound to the address it began on session_bind_to_address (0, 1 or 2 as above) Off - it ends a phone's session when it moves from wi-fi to mobile hmctl settings set --set session_idle_seconds=900 --set session_bind_to_address=true hmctl domain update example.com --set session_lifetime_seconds=28800 --set session_one_per_device=1 A domain's value wins over the server's where it is set. Nothing changes on the day you upgrade: every policy reads 0, which means the ceilings the server has always had. The cookie, hmailsession, is HttpOnly and SameSite=Strict, and Secure whenever the browser came over HTTPS; a proxy that terminates HTTPS in front of the loopback listener should set X-Forwarded-Proto: https. Browser sessions live in the server's memory, so a restart ends every one, and a password change ends every session and drops every connection of the account. What none of this does It does not reach the mail protocols. A phone's IMAP and SMTP still take the password or an app password, and an account that only ever signs in through its identity provider needs a password set before its person can mint an app password. No password-less accounts: a passkey sits beside the password, and no policy forbids the password. Where to read more Wiki: Sign-in and Identity, Connecting a Mail Client, Security Hardening. Documents: PasskeysAndSingleSignOn.md, AccountRecovery.md, DevicesAndSessions.md, PasswordBreachCheck.md, and item 24 of the 6.3.4 section of the CHANGELOG. The public documentation: 17.2 Modern authentication. On this forum: Connecting a mail client for app passwords on a phone, Active Directory and LDAP sign-in, encryption at rest, Upgrading to 6.3.4, The Control Deck, The webmail portal at /portal and the 6.3.4 announcement.
  • 0 Votes
    1 Posts
    132 Views
    ProgressiverobotP
    Written for 6.3.4. hMailServer 6.3.8 (2 November 2026) has the web services listener, which serves CalDAV and CardDAV, refuse an address an auto-ban or an IP range bans: it closes the connection with no answer, before the TLS handshake, when the IP range that decides for the address allows none of SMTP, POP3 and IMAP, where up to 6.3.7 a banned address could go on guessing passwords there. So an IP range of your own that allows some addresses nothing now closes CalDAV and CardDAV to them as well: check that these services' clients do not come from one. Loopback and the addresses in WebServicesTrustedProxies are never refused by a range, and a failed sign-in from one of them is counted against the name alone (AccountLockoutThreshold, off by default), never towards a ban of that address. A client that signs in with another spelling of the account's address than the stored one, in more than the case of the letters A to Z, or with white space at either end, may no longer sign in. See the 6.3.8 announcement. This topic covers what hMailServer 6.3.4 serves to a phone or a desktop client over CalDAV and CardDAV, and what the webmail's Calendar, Tasks, Contacts and Directory pages do with the same data. The one thing to know first: CalDAV and CardDAV are served on the web-services listener, they take HTTP Basic as the account - its password or an app password - over HTTPS only, and a passkey or single sign-on does not reach them. CardDAV has been there since 6.3.3 with one address book; calendars, several and shared address books, the directory and rooms are all since 6.3.4. Setting up a phone or a desktop client A client given the mail address finds the server by discovery: /.well-known/carddav and /.well-known/caldav redirect to /dav/, a PROPFIND there answers the principal, and the principal answers the address-book home and the calendar home. iOS, macOS Contacts and Calendar, DAVx5 on Android and Thunderbird all sync what is there. The IMAP logon's lockout and auto-ban apply to the sign-in. Which password. An account with a TOTP second factor enrolled uses an app password, because once a secret exists the account password stops being a mailbox credential. Minting an app password on the webmail's Security page asks for the account's own password again, so an account that single sign-on or SCIM made, whose random password nobody was told, needs a password set by an administrator before its person can make an app password for a phone. The listener. The web services are off as shipped: WebServicesHttpPort and WebServicesHttpsPort default to 0, and the HTTPS port takes WebServicesCertificateFile and WebServicesPrivateKeyFile. Since 6.3.4 these keys are kept in the database: change them in the Control Panel (Windows), over REST, or on Linux with hmailserver --set-setting NAME=VALUE, not by editing hMailServer.ini. A saved port or certificate waits for a reinitialise or a restart, since the listener binds when it starts. On the Devices list a DAV client appears with the protocol dav and its user agent. It has no connection to drop - each request authenticates afresh - so revoking it means revoking the app password behind it. Collection What it is /dav/calendars/<address>/calendar/ The default calendar, Calendar, made the first time it is asked for /dav/calendars/<address>/<name>/ Another calendar of the account's own, made in the webmail or with MKCALENDAR /dav/calendars/<address>/shared-<id>/ A calendar another account, or a group, shares with this one /dav/addressbooks/<address>/contacts/ The default address book, where a client that has synced since 6.3.3 still finds it /dav/addressbooks/<address>/book-<id>/ Another book of the account's own (schema 6079) /dav/addressbooks/<address>/shared-<id>/ A book shared with this account, or with the whole domain /dav/addressbooks/<address>/directory/ The organisation's directory, read-only (schema 6078) Each calendar collection advertises VEVENT and VTODO, so a phone's calendar and its reminders list are the same collection. Calendars, events and tasks An account has one calendar until it makes more. The Calendars box beside the grid adds one with a name and a colour (POST /api/v1/me/calendars), renames or recolours it and deletes it with its events; the default cannot be deleted. Over CalDAV the colour travels as Apple's calendar-color, so a phone shows the same one, and PROPPATCH sets the name and colour from the phone's side. Events. An event takes a title, start and end as wall-clock times in tz (an IANA zone the server knows), all_day, a location and notes, rrule (daily, weekly on chosen days, monthly, yearly, with an interval and an end by count or by date), alarm_minutes and guests. The server writes DTSTART;TZID=<zone> so a weekly 09:00 stays 09:00 across a clock change, and a VALARM for the reminder, which is what CalDAV clients ring for. A repeating event is expanded across daylight saving in any of the 592 zone names the server knows; calendar-query with a time range, calendar-multiget and sync-collection are all answered, with ETags and If-Match. Changing one occurrence alone writes an override; deleting one writes an EXDATE on the master. Tasks. The Tasks page lists every VTODO of every calendar the account can see, soonest due first. Ticking one off writes STATUS:COMPLETED, COMPLETED and PERCENT-COMPLETE:100 as a client does. A second calendar is a task list of its own. Sharing a calendar, and delegates The owner names another account of this server and whether it may write (PUT /api/v1/me/calendars/{id}/shares/{address} with write). The other account sees the calendar in its own Calendars box and in its CalDAV home as shared-{id}/, with owner pointing at the sharer's principal and a current-user-privilege-set of read, or read and write. A read-only share takes no PUT, no event from the page and no PROPPATCH. The sharee leaves with a CalDAV DELETE of shared-{id}/ or from the page. Shared with a group. Since schema 6086 the name may be one of the server's groups (hm_groups) instead of an address; a name that is both is the account. The membership is read at the moment a member asks, so somebody added later sees the calendar and somebody removed no longer does. A member cannot leave a group share (403): they leave the group, or the owner takes the share back by the group's name. A delegate. A share may be a delegation (delegate: true, which implies write the sharee may answer the owner's invitations on the owner's behalf. The answer is POST /api/v1/me/calendar/events/{id}/reply with partstat ACCEPTED, TENTATIVE or DECLINED, allowed to the owner and to a delegate and refused (403) to a plain writer or a reader. The organiser receives a METHOD:REPLY whose ATTENDEE carries SENT-BY="mailto:<delegate>", From: the owner with Sender: the delegate. Free-busy An account of the domain can see when a colleague is busy, and nothing else about them. GET /api/v1/me/freebusy?address=<account>&from=<when>&to=<when> answers the busy periods of that account's own calendars - not the ones shared with it - over a window of at most 62 days (400 beyond). An event that is TRANSP:TRANSPARENT or STATUS:CANCELLED, or that the account declined, is left out; a period is tentative when the account's PARTSTAT is TENTATIVE or NEEDS-ACTION, else busy. No title, location or description is answered. Another hosted domain's account, an address nobody has, an inactive account, and an account whose freebusy preference is nobody are one and the same 404, so the route enumerates nothing. The preference is the person's own, in Settings: "Who may see when you are busy". Over CalDAV the same computation answers the free-busy-query REPORT on a calendar the caller may read, as one bare VFREEBUSY, over a time-range that needs both bounds and spans at most 62 days. The webmail's event editor draws an availability strip for the guests of a meeting from the same computation. Invitations by mail (iMIP) An event with guests, saved with "Send invitations by e-mail", mails each guest the event as a text/calendar part with method=REQUEST (RFC 6047) - what Outlook, Gmail and Apple Mail show as an invitation card. Saving a change mails an update with the sequence stepped; deleting the event mails METHOD:CANCEL. A message that carries a text/calendar part or an .ics attachment shows a card with Accept, Maybe, Decline and "Propose a new time" (METHOD:COUNTER). Filed on delivery. A METHOD:REQUEST delivered to an account is filed into its default calendar as it arrived (NEEDS-ACTION as a rule) when, and only when, it names the account as an ATTENDEE and not as the organiser. A message flagged as spam files nothing; a REQUEST naming somebody else is not kept; and an account that has never opened a calendar gets nothing filed. A stored copy under the same UID is replaced only by the same or a later SEQUENCE, and never when its ORGANIZER is not the invitation's. A REPLY delivered to the organiser sets that attendee's PARTSTAT in the stored event, so every phone syncing the calendar shows who accepted without anyone opening the mail; a CANCEL delivered to a guest marks the stored copy cancelled. Rooms and equipment (since 6.3.4) A room or a projector is an account whose kind says so: person (the default), room or equipment, a column of hm_accounts (schema 6101). It is invited like any guest and answers by itself, from its own address, by its booking policy; what it does not decide it leaves to its delegates. Where What The Control Deck The account editor's General group has a select of the three kinds; a room or equipment gets a Booking policy card REST kind on GET and PUT /api/v1/accounts/{address}; the policy at GET and PUT /api/v1/accounts/{address}/booking-policy COM (Windows) Account.Kind (0 person, 1 room, 2 equipment), saved by Save(); the booking policy is not on COM The Windows Control Panel Does not show it: a room is an ordinary account to it From the moment its kind is saved every sign-in as the room is refused, whatever the credential, with the ordinary wrong-password answer. A room with no policy row answers by the defaults: Field Default What it decides auto_accept true Answer invitations by this policy; false leaves every one for the delegates window_days 180 How many days ahead a booking may start, 1 to 1080 max_duration_minutes 1440 The longest booking; 0 is no limit working_hours_only false Only inside the working hours, on the working days work_days, work_start, work_end Monday to Friday, 08:00 to 17:00 The working days and hours time_zone UTC The zone the working hours are in, such as Europe/London allow_conflicts false Accept a booking over another one allow_recurring true Accept a recurring meeting bookers empty Empty is everyone in this server's own domains; a list is an address, @domain, a distribution list's address or a group's name. An organiser from outside must be named here capacity 0 How many people the room seats, shown in the directory; it decides nothing delegates none Addresses and group names, each given a delegate share of the room's calendar Who is asking. The room first decides whether the invitation's ORGANIZER is who sent it, from what the server recorded when the message arrived and never from a From: header or a MAIL FROM. An organiser at this server's own domains is believed only when the message was submitted by a signed-in session as that account, or as one allowed to send as it; a message sent through an API key is not believed. An organiser from outside is believed only when bookers names the address or its @domain and the message passed DMARC for that domain. SPF on its own is not enough, and with DMARC off no outside organiser is ever believed. Anything else is neither filed nor answered, and the delegates are told, within limits. The answer is decided just after delivery, on a work queue of the rooms' own; no delivery and no SMTP answer waits for it. In order: who is asking; may they book; sent automatically (an empty reverse path or Auto-Submitted, never answered); auto_accept; at most 20 answers an hour to one organiser; recurrence; the window; the longest booking; working hours; conflicts, counting every booking the room holds that is not cancelled as busy. Accepted, the booking is filed and a METHOD:REPLY goes from the room's address, marked Auto-Submitted: auto-replied. Declined, the reply carries the reason alone; a conflict says when, never whose. A request left for the delegates is never put into the room's calendar on the sender's word. A room with no delegates keeps what it leaves and tells nobody: name at least one. Two servers sharing one database are not serialised for rooms: run rooms on one node. Finding a room. The directory lists a room with its kind and capacity, and its CardDAV card says KIND:location (KIND:device for equipment). Every CalDAV principal says its calendar-user-type - INDIVIDUAL, ROOM or RESOURCE - and the principal-property-search REPORT on /dav/principals/, which is what a client's room finder sends, answers the domain's rooms and equipment. Apple Calendar searches for locations this way; Thunderbird has no room finder. Every request stays in the room's mailbox, and a full mailbox answers nothing more, so give a room a quota and a MessageRetentionDays that suit it. Contacts, groups and several address books Kept, and other contacts. A contact has a source: manual for what the person added, imported or kept, and collected for every address the person has written to - from the webmail's send and, since 6.3.4, from a mail client over an authenticated SMTP submission. A collected address is offered under Other contacts and by the To, Cc and Bcc completion, and nowhere else: CardDAV serves only what the person kept, so a phone's address book is not filled with everyone ever written to. Keep makes it a contact. What a card carries. A card a CardDAV client sends is kept as sent and served back byte for byte. The page reads birthday (BDAY), note, phones (every TEL) and the embedded PHOTO; a photo that is only a URL is not the server's to fetch. Groups. A group is a card of kind group - KIND:group in vCard 4.0, X-ADDRESSBOOKSERVER-KIND:group in 3.0 - so a group made on the phone appears in the webmail and the reverse. A group's name in To becomes that group's recipients, so the reader sees the people and not the group. The ceilings hold on both surfaces: 500 members in one group (a card past it is refused 403 CARDDAV:valid-address-data) and 200 groups in one address book (400 over REST, 507 Insufficient Storage over CardDAV). A group is not a distribution list: nothing about it reaches SMTP. Several address books, and shared ones. Since schema 6079 the account starts with one book, Contacts, which cannot be deleted, and may have at most 50. The owner shares one from the Contacts page with an address - one account of this server - or with its own domain's name; Can edit makes the share writable, and a book takes at most 200 shares. A domain-wide share aimed at another hosted domain is 403, and a person cannot leave a domain share: only the owner or an administrator removes it. One account holds one address once across all its books, so a contact is moved between books, never copied. Over CardDAV a read-only collection grants read and read-current-user-privilege-set and nothing else, a PUT into it is 403, and DELETE on the collection gives the share back and deletes nobody's contacts. MKCOL is refused: a book is made from the Contacts page or /api/v1/me/address-books. No group is shared, in either direction. The backup carries the books and their contacts; hm_addressbookshares is not carried, so the sharing is set again. The Control Panel and the Control Deck have no page for this. An administrator has GET /api/v1/accounts/{address}/address-books, which shows the shape of the sharing and never the contacts, and a route that removes one share, recorded as address-book-share-removed. Import and export. The Contacts page imports a vCard file or a CSV, including Google's and Outlook's exports. Export as vCard downloads contacts.vcf; Export as CSV downloads contacts.csv and leaves groups out. The organisation's directory It is one domain at a time, and always the reader's own: every active account of the domain whose owner has not opted out, and every enabled distribution list, with what is set on top in hm_directory (schema 6078) - job title, department, work telephone, mobile, office, a photo (JPEG, PNG or GIF, at most 100 KB decoded, judged by its bytes), and a note that is the administrator's alone. Two switches, both rows of hm_settings and both on by default. DirectoryEnabled is whether the directory is answered at all; turn it off on a server whose one domain holds people who are not one organisation. With it off the webmail hides its Directory page and the CardDAV collection stays, empty, so a subscribed phone syncs to nothing rather than failing every poll. DirectoryUserEditable is whether a person may set their own fields. The opt-out is never what a switch takes away, and it is not invisibility: mail still arrives and the address still works. An administrator changing somebody's opt-out is on the audit trail as directory-opt-out-changed. The surfaces. The webmail's Directory page (#/directory); the To completion, which offers the reader's own contacts first and the directory hits after; GET /api/v1/me/directory?q=&limit= for the person and GET and PUT /api/v1/accounts/{address}/directory for the administrator; a Directory entry tab in the Windows Control Panel's account dialog, and the same card in the Control Deck; and the read-only CardDAV collection, where PUT, DELETE and PROPPATCH are refused 403. Nothing is learned from anywhere else: there is no import from Active Directory or LDAP, even on a server that authenticates against one, and no LDAP server for a mail client to search. What it does not do No RFC 6638 scheduling (no schedule-inbox or schedule-outbox), no free-busy of a calendar on another server, no per-calendar time zone (calendar-timezone is not kept), no partial calendar-data, and no time-range filter on a VALARM. No ActiveSync and no Exchange Web Services: a phone uses IMAP and SMTP for mail, and CalDAV and CardDAV for the rest. Calendar shares and address-book shares are not in the backup, so set them again after a restore; the booking policy, the books, their contacts and the directory rows are. Where to read more Wiki: Connecting a Mail Client, Rooms and Equipment, The Webmail, Ports Reference. Documents: Calendar.md, Contacts.md, SharedAddressBooks.md, Directory.md, RoomsAndEquipment.md. On this forum: The webmail portal at /portal, The Control Deck, Signing in, Connecting a mail client, and the 6.3.4 announcement.