Base URL
All API endpoints are served from the Exchange HTTP API. The base URL depends on your configuration:
https://mail.molodetz.nl:9003
All request and response bodies use Content-Type: application/json unless otherwise noted. CORS headers are included on all responses.
Authentication
The API uses stateless JWT tokens. Obtain a token by posting credentials to /auth/login, then include it as a Bearer token in subsequent requests.
Login
curl -s -X POST https://mail.molodetz.nl:9003/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"secret","domain":"example.com"}'
Response:
{
"token": "eyJ1Ijoi...",
"username": "alice",
"domain": "example.com",
"expires_in": 3600
}
The username field also accepts alice@example.com format; in that case domain can be omitted.
Using the token
curl -s https://mail.molodetz.nl:9003/folders \
-H "Authorization: Bearer $TOKEN"
Admin access
Certain endpoints (user management, alias management) require admin privileges. A user is admin when their admin field is true in their account record. The first admin user must be created via CLI:
python -c "import asyncio; from rmail import auth; asyncio.run(auth.create_user('./mailstore', 'admin', 'example.com', 'password', admin=True))"
After that, admin users can create other admin users through the API.
Token configuration
The JWT signing secret is set in config.json under exchange_api.token_secret. If omitted, a random secret is generated on each server start (tokens will not survive restarts). Set a fixed value for persistent sessions:
{
"exchange_api": {
"token_secret": "your-64-char-hex-string"
}
}
Error responses
Errors return a JSON body with an error field:
{"error": "Authentication required"}
| Status | Meaning |
|---|---|
| 400 | Bad request (invalid JSON, validation error) |
| 401 | Missing or invalid token |
| 403 | Insufficient privileges (admin required) |
| 404 | Resource not found |
| 413 | Payload too large / quota exceeded |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Rate limiting
Per-IP rate limiting is enabled by default. Auth failures trigger a lockout after 5 attempts (configurable). Localhost is whitelisted by default.
Endpoint Reference
POST /auth/login
Auth: None
| Field | Type | Required | Description |
|---|---|---|---|
| username | string | Yes | Username or email (user@domain) |
| password | string | Yes | Account password |
| domain | string | No | Domain (inferred from username@ or first configured domain) |
GET /folders
Auth: User
curl -s https://mail.molodetz.nl:9003/folders -H "Authorization: Bearer $TOKEN"
Response: {"folders": [{"name": "INBOX", "total": 42, "unread": 5}, ...]}
GET /folders/{folder}/messages
Auth: User
| Param | Type | Default | Description |
|---|---|---|---|
| offset | int | 0 | Skip first N messages |
| limit | int | 100 | Max messages to return (1-1000) |
curl -s "https://mail.molodetz.nl:9003/folders/INBOX/messages?offset=0&limit=25" \
-H "Authorization: Bearer $TOKEN"
POST /folders/{folder}/messages
Auth: User. Append a raw RFC822 message to the folder. Body is raw bytes, not JSON.
curl -s -X POST https://mail.molodetz.nl:9003/folders/Drafts/messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: message/rfc822" \
--data-binary @message.eml
GET /folders/{folder}/messages/{uid}
Auth: User. Returns raw RFC822 message bytes with Content-Type: message/rfc822.
DELETE /folders/{folder}/messages/{uid}
Auth: User. Permanently deletes the message.
curl -s -X DELETE https://mail.molodetz.nl:9003/folders/INBOX/messages/42 \
-H "Authorization: Bearer $TOKEN"
PATCH /folders/{folder}/messages/{uid}/flags
Auth: User. Replaces the message's IMAP flags.
curl -s -X PATCH https://mail.molodetz.nl:9003/folders/INBOX/messages/42/flags \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"flags":["\\Seen","\\Flagged"]}'
POST /messages/send
Auth: User. Compose and deliver an email to local recipients. A copy is saved to Sent.
curl -s -X POST https://mail.molodetz.nl:9003/messages/send \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"to":["bob@example.com"],"subject":"Hello","body":"Message body."}'
| Field | Type | Required | Description |
|---|---|---|---|
| to | string[] | Yes | Recipient email addresses |
| subject | string | No | Message subject |
| body | string | No | Plain text body |
Alias Management
GET /aliases
Auth: User. Lists regex alias rules for the authenticated user's domain.
POST /aliases
Auth: Admin.
curl -s -X POST https://mail.molodetz.nl:9003/aliases \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"pattern":"sales-.*","target":"bob"}'
DELETE /aliases/{pattern}
Auth: Admin. URL-encode the pattern.
Admin — User Management
All endpoints require an admin JWT token (user with admin: true).
GET /admin/users?domain=example.com
List all users in a domain. Domain defaults to admin's own domain if omitted.
curl -s "https://mail.molodetz.nl:9003/admin/users?domain=example.com" \
-H "Authorization: Bearer $ADMIN_TOKEN"
Response:
{
"users": [
{
"username": "alice",
"domain": "example.com",
"enabled": true,
"admin": false,
"quota_mb": 1024,
"created": "2025-01-15T10:30:00+00:00"
}
]
}
POST /admin/users
Create a new user account.
curl -s -X POST https://mail.molodetz.nl:9003/admin/users \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"bob","password":"secret123","domain":"example.com","admin":false,"quota_mb":2048}'
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| username | string | Yes | Lowercase, a-z0-9._- | |
| password | string | Yes | 1-1024 bytes | |
| domain | string | Yes | Must be a valid domain | |
| admin | bool | No | false | Grant admin privileges |
| quota_mb | int | No | 1024 | Mailbox quota in MB (1-1048576) |
GET /admin/users/{username}?domain=example.com
Get a single user's details.
PATCH /admin/users/{username}?domain=example.com
Update user fields. Only provided fields are changed.
curl -s -X PATCH "https://mail.molodetz.nl:9003/admin/users/bob?domain=example.com" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'
| Field | Type | Description |
|---|---|---|
| enabled | bool | Enable/disable the account |
| admin | bool | Grant/revoke admin privileges |
| quota_mb | int | Mailbox quota in MB |
DELETE /admin/users/{username}?domain=example.com
Permanently delete a user and all their data.
curl -s -X DELETE "https://mail.molodetz.nl:9003/admin/users/bob?domain=example.com" \
-H "Authorization: Bearer $ADMIN_TOKEN"
PUT /admin/users/{username}/password?domain=example.com
Change a user's password.
curl -s -X PUT "https://mail.molodetz.nl:9003/admin/users/bob/password?domain=example.com" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"password":"new-secure-password"}'
CalDAV
Full CalDAV server with read-write event access, sync tokens, recurrence, free-busy, and iMIP scheduling. Authentication uses HTTP Basic (user@domain:password).
Discovery
/.well-known/caldav 301-redirects to the principal collection per RFC 6764. Clients can autodiscover by configuring the email address only.
Allowed methods
OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, MKCALENDAR, PROPPATCH
OPTIONS returns DAV: 1, 2, 3, calendar-access, calendar-schedule, addressbook.
PROPFIND
Honors the Depth header (0, 1, infinity). Returns standard properties plus D:current-user-principal, C:calendar-home-set, C:calendar-user-address-set, CS:getctag, D:sync-token, I:calendar-color.
REPORT methods
- calendar-multiget — fetch multiple events by URL list in one round trip
- calendar-query — filter by
C:time-range; recurring events are expanded server-side via RRULE before filtering - sync-collection — incremental sync via opaque
D:sync-token; returns adds/changes plus deletions as 404 responses - free-busy-query — returns a
VFREEBUSYover a time range
PUT (create or update event)
curl -k -u alice@ex.com:secret -X PUT \
-H "Content-Type: text/calendar" \
--data-binary @event.ics \
https://mail.molodetz.nl:9003/caldav/alice/calendars/default/MY-UID.ics
Honors If-None-Match: * (create-only) and If-Match: "etag" (lost-update protection). Returns the new ETag.
DELETE
Removes an event or an entire calendar. Honors If-Match.
MKCALENDAR
Creates a new calendar collection. Optional XML body sets D:displayname, I:calendar-color, C:calendar-description.
PROPPATCH
Updates calendar metadata (display name, color, description). Returns a multi-status with per-property success.
Sync tokens & ETags
Each calendar has an opaque sync_token that rotates on every write. ETags are content-hashed (SHA-256, 32 hex chars). Calendar ctag is a monotonic counter — clients use it for cheap "did anything change?" checks.
Recurrence
RRULE expansion uses python-dateutil. Time-range REPORTs return matching occurrences. RECURRENCE-ID overrides are stored alongside the master event in the same .ics file.
iMIP scheduling
Events with ATTENDEE properties trigger outgoing METHOD:REQUEST emails via the relay queue. Incoming text/calendar attachments in delivered email are auto-imported into the recipient's Schedule-Inbox calendar (uses RFC 6047 dispatch).
Reminders
A background scheduler scans every 60 seconds for VALARM components whose TRIGGER falls in the upcoming window, expanding RRULEs as needed, and delivers a reminder email to the user's INBOX.
Storage layout
{mail_root}/domains/{domain}/users/{username}/calendars/
├── default/
│ ├── calendar.json (name, color, description)
│ ├── index.json (ctag, sync_token, per-event metadata, tombstones)
│ └── events/
│ ├── {uid}.ics
│ └── ...
├── Schedule-Inbox/ (incoming invites land here)
└── ...
Index reads are lock-free; writes use fcntl.flock() on a sidecar lockfile and atomic os.rename().
curl -k -u "alice@example.com:password" \
-X PROPFIND -H "Depth: 1" https://mail.molodetz.nl:9003/caldav/alice/calendars/
Drive (per-user file storage)
Each user has a private file store at https://mail.molodetz.nl:9003/drive/{path}. Same authentication as the rest of the API: HTTP Basic with Base64-encoded email:password, or Bearer JWT. The Basic flow makes the drive usable from arbitrary HTTP clients (curl, browsers via password prompt, automation scripts).
Auth example
EMAIL_B64=$(printf "alice@example.com:secret" | base64)
curl -k -H "Authorization: Basic $EMAIL_B64" https://mail.molodetz.nl:9003/drive/
Allowed methods
OPTIONS, GET, HEAD, PUT, POST, DELETE, MOVE, MKCOL, PROPFIND
Endpoints
| Method | Path | Behaviour |
|---|---|---|
| GET | /drive/ | JSON listing of root directory |
| GET | /drive/{path} | Directory listing OR file content (depending on path type). Honors If-None-Match for files (returns 304). |
| HEAD | /drive/{path} | Metadata only. |
| PUT | /drive/{path} | Create or overwrite a file. Auto-creates missing parent directories. Honors If-Match and If-None-Match: *. |
| POST | /drive/{path}?action=mkdir | Create a directory and any missing parents. |
| POST | /drive/{path} | multipart/form-data upload. Each filename= part lands at {path}/{filename}. Filenames may contain / for nested upload — useful for folder uploads from browsers using <input type="file" webkitdirectory>; each path component is validated independently so traversal remains impossible. |
| MKCOL | /drive/{path} | WebDAV-style mkdir. |
| DELETE | /drive/{path} | Delete a file or recursively a directory. |
| MOVE | /drive/{src} | With Destination: /drive/{dst} header. Rename or relocate. Overwrite: F blocks clobbering. |
| PROPFIND | /drive/{path} | JSON metadata + child listing for directories, metadata for files. |
Storage layout
Each user's drive lives at {mail_root}/domains/{domain}/users/{username}/drive/ with this structure:
drive/
├── _meta.json (ctag, sync_token, total_files, total_dirs, total_bytes)
└── files/
├── _index.json (per-directory index, see below)
├── _index.lock (fcntl flock sidecar for atomic updates)
├── docs/
│ ├── _index.json
│ └── readme.txt
└── photos/
├── _index.json
└── 2026/
├── _index.json
└── trip.jpg
Per-directory index
Every directory has an _index.json with both self counters (children at this level) and total counters (recursive). Updates propagate from the leaf up to the root on every write, so any directory's totals are read-only and O(1):
{
"version": 1,
"name": "photos",
"ctag": 12,
"mtime": 1777191634.93,
"self_files": 1, "self_dirs": 1, "self_bytes": 4823,
"total_files": 5, "total_dirs": 3, "total_bytes": 1842917,
"children": {
"2026": {"type": "dir", "mtime": 1777191634.4},
"thumb.jpg": {"type": "file", "size": 4823, "etag": "\"...\"",
"mtime": 1777191634.93, "content_type": "image/jpeg"}
}
}
Writes use fcntl.flock() on a sidecar _index.lock file. File contents are written via temp + atomic os.rename(). ETags are content-hashed (SHA-256, 32 hex chars).
Path validation
Every path component is rejected if it contains .., /, \\, null bytes, or control characters. Reserved names (_index*, _meta.json, *.tmp) are blocked. Maximum path depth is 64 components, max component length 255 bytes (filesystem limit).
Quota integration
Drive bytes count toward the user's quota_mb alongside mailbox storage. Exceeding quota returns 413. The user-storage cache is invalidated on every drive write so quota checks stay accurate.
Webmail UI
Tile-based browser at https://mail.molodetz.nl:9003/webmail/drive/ with three upload controls in the toolbar:
- upload files — pick one or more individual files to upload to the current directory.
- upload folder — pick a folder; the browser sends every file inside it (recursively) with the relative path embedded in the multipart filename, and the server reproduces the directory structure on disk.
- new folder — type a name and submit to create a (possibly nested) directory.
Other features: breadcrumb navigation, folder tiles, file-type icons (photo, audio, video, archive, document, code), per-tile delete with confirmation, multi-select bulk delete via the same :has(input:checked) CSS pattern as the mail list, and a stats footer showing files / folders / bytes.
To avoid HTML5's nested-form restriction the bulk and per-tile delete forms are siblings of the toolbar; checkboxes and per-tile × buttons attach to them via the form="..." attribute.
Scraping selectors
section.drive-grid[data-path][data-total-files][data-total-dirs][data-total-bytes], .tile-wrap[data-path][data-type], .tile-name, .tile-meta, .drive-breadcrumb .crumb, .drive-stats, .tile-dir / .tile-file.
Sharing
Any file or folder (except the drive root) can be shared via a public link with a CRUD permission set. The link has the form https://mail.molodetz.nl:9003/drive/{uid}/{name}; the {name} segment is decorative — resolution uses only the random 32-character UID. Sub-paths under a shared folder use https://mail.molodetz.nl:9003/drive/{uid}/{name}/{subpath}.
Permission letters:
| Letter | Allows |
|---|---|
| R | Read file content / list directory (always implicit; included automatically) |
| C | Create new files via PUT, new directories via POST/MKCOL (only meaningful for directory shares) |
| U | Overwrite existing files |
| D | Delete files / sub-directories beneath the share root (the share root itself cannot be deleted via the link) |
Share API
| Method | Path | Behaviour |
|---|---|---|
| POST | /drive/_share | Create a share. JSON body: {"target_path":"docs/foo.txt","name":"Foo","permissions":"RU","expires":null}. Name defaults to the last component of the target. Returns the share record including the public URL. |
| GET | /drive/_share | List the caller's shares. |
| GET | /drive/_share/{uid} | Get a single share's metadata (owner-only). |
| PATCH | /drive/_share/{uid} | Update name/permissions/expiry (owner-only). |
| DELETE | /drive/_share/{uid} | Revoke a share. |
Share access
The same drive verbs work against the shared resource without authentication, gated by the share's permissions:
- GET / HEAD / PROPFIND — requires
R. - PUT creating new file — requires
C; PUT overwriting existing — requiresU. - POST multipart upload — requires
U(and the parent dirs are auto-created). - POST?action=mkdir / MKCOL — requires
C. - DELETE — requires
D; cannot delete the share root via the link. - MOVE — not allowed on shared resources.
EMAIL_B64=$(printf "alice@example.com:secret" | base64)
# Create a public read-only link to a file
curl -k -X POST -H "Authorization: Basic $EMAIL_B64" \
-H "Content-Type: application/json" \
--data '{"target_path":"docs/budget.xlsx","name":"Q3 Budget","permissions":"R"}' \
https://mail.molodetz.nl:9003/drive/_share
# Share a folder with read+create+delete permission, no overwrites
curl -k -X POST -H "Authorization: Basic $EMAIL_B64" \
-H "Content-Type: application/json" \
--data '{"target_path":"projects/alpha","permissions":"RCD"}' \
https://mail.molodetz.nl:9003/drive/_share
# Anyone can download the file (no auth needed)
curl -k https://mail.molodetz.nl:9003/drive/QZGu_wPlp_OXbgKmw4aPreneMO3-scJi/Q3%20Budget
Storage layout for shares
{mail_root}/shares/{uid[:2]}/{uid}.json (sharded by 2-char prefix; one file per share)
{mail_root}/domains/{domain}/users/{username}/drive/_shares.json (per-owner index of UIDs)
Lookup by UID is O(1); listing the caller's shares is O(N_owner). Each per-share file holds the entire share record (owner, target_path, name, permissions, created/expires timestamps); the per-owner index holds only UIDs.
Path-traversal safety in shares
- Share UIDs are 32 chars of URL-safe base64 — un-guessable, statistically collision-free, and structurally distinct from real folder names.
- Any folder name beginning with
_is reserved byDrivePath, so user folders cannot collide with_share,_shared,_index,_meta, etc. - The share name in the URL is validated against
[A-Za-z0-9 ._\-()\[\]]{1,128}— no slashes, no dots, no control characters. - Subpaths after the name are passed through
DrivePath, which rejects..as a component, null bytes, control characters, and absolute paths. - The full disk path is always share.target_path + validated subpath, so the resolved location is always under the owner's drive and within the shared subtree.
- DELETE on the share root is explicitly refused even when the share holds
D.
Webmail UI for sharing
- Each tile in the drive browser exposes a 🔗 share icon next to the × delete icon. Clicking opens
/webmail/drive/{path}/_sharewith a form to choose a link name and CRUD permissions, plus a list of existing shares for that object. - The global shared link in the nav and sidebar opens
/webmail/drive/_shared, listing every share the user has created with a copyable link, permission badges, and a revoke button. - The shared-directory page reuses the same file-manager component as the owner's drive view: tile grid, breadcrumb, file-type icons, multi-select checkboxes with the bulk-actions toolbar (using
:has(input.row-check:checked)), per-tile delete, recursive-totals stats line, and the upload-files / upload-folder / new-folder controls. Each control is conditionally rendered based on the share's CRUD permissions, so visitors only see actions they are allowed to perform; checkbox values are share-relative paths so the owner's directory layout is not exposed. - When a browser hits the public link
/drive/{uid}/{name}, the server inspects theAcceptheader. For directories withtext/htmlinAccept, the response is a 302 redirect to/webmail/drive/{uid}/{name}(the HTML file-manager view). For files, content is streamed inline with the correctContent-TypeandETag. API clients (notext/htmlin Accept) receive JSON listings as documented above — no redirect.
Webmail Calendar UI
Available at https://mail.molodetz.nl:9003/webmail/calendar. Supports four views — month, week, day, agenda — switchable via /webmail/calendar/{view}. Multi-calendar visibility is per-user persistent.
- Create:
GET /webmail/calendar/event/newrenders a form;POSTsaves and triggers iMIP if attendees are listed. - Edit:
/webmail/calendar/event/{calendar}/{uid}/edit - Delete:
POST /webmail/calendar/event/{calendar}/{uid}/delete - RSVP:
POST /webmail/calendar/event/{calendar}/{uid}/respondwithresponse=ACCEPTED|DECLINED|TENTATIVEupdates PARTSTAT and emails aMETHOD:REPLYback to the organizer. - Import:
POST /webmail/calendar/importtakes a rawtext/calendarbody. - Toggle visibility:
POST /webmail/calendar/visibilitywithcalendar=namepersists topreferences.jsonascalendar_visible.{name}.
Scraping selectors: .cal-grid.month, .cal-grid.week, .cal-grid.day, .agenda-list, .cal-event[data-uid][data-calendar], .event-detail[data-uid][data-calendar], .cal-row[data-calendar][data-visible].
Webmail
A server-rendered HTML webmail interface is available at:
https://mail.molodetz.nl:9003/webmail/login
Features: folder browsing, message reading with HTML rendering, attachments, compose, reply, forward, delete, move, flag management, calendar view, and user registration.
Scraping and Automation
The webmail HTML is designed to be machine-readable. All links use absolute URLs (including scheme, hostname, and port), so scrapers do not need to resolve relative paths. Semantic HTML5 elements and data attributes provide stable selectors for automated extraction.
Page structure
| Element | Selector | Description |
|---|---|---|
| Sidebar | <aside class="sidebar"> | Folder navigation panel |
| Main content | <main class="content"> | Primary content area |
| Current user | <span class="current-user"> | Logged-in email address |
Folder list (sidebar)
Each folder is an <li> with class and data attributes:
<li class="folder-item active"
data-folder="INBOX"
data-total="42"
data-unread="5">
<a href="https://mail.example.com:9003/webmail/INBOX">INBOX <span class="count">5</span></a>
</li>
| Attribute | Description |
|---|---|
data-folder | Folder name |
data-total | Total message count |
data-unread | Unread message count |
.active | Present when this folder is selected |
Message list page
The message table is wrapped in a <section> with folder metadata:
<section class="message-list" data-folder="INBOX" data-total="42">
Each message is a table row with structured data:
<tr class="message-row unread flagged"
data-uid="137"
data-folder="INBOX"
data-flags="\Flagged">
<td class="col-check">...</td>
<td class="col-star flagged">...</td>
<td class="col-from"><span class="message-from">alice@example.com</span></td>
<td class="col-subject"><a class="message-link" href="..."><span class="message-subject">Hello</span></a></td>
<td class="col-date"><time class="message-date">Apr 13</time></td>
</tr>
| Selector | Description |
|---|---|
tr.message-row | Each message row |
tr.unread | Message has not been read |
tr.flagged | Message is starred/flagged |
data-uid | IMAP UID of the message |
data-folder | Containing folder name |
data-flags | Space-separated IMAP flags |
.message-from | Sender display name or address |
.message-subject | Subject line text |
.message-date | Date display string |
a.message-link | Absolute URL to message detail page |
Pagination
<span class="page-info" data-total="42" data-offset="0" data-limit="50">1–42 of 42</span>
<nav class="pagination">
<a class="pagination-prev" href="...">...</a>
<a class="pagination-next" href="...">...</a>
</nav>
Message detail page
The message body is wrapped in an <article> with metadata:
<article class="msg-detail" data-uid="137" data-folder="INBOX">
<header class="msg-header">
<h2 class="msg-subject">Hello</h2>
<div class="msg-meta">
<div class="msg-from">...<span class="message-from">alice@example.com</span></div>
<div class="msg-to">...<span class="message-to">bob@example.com</span></div>
<div class="msg-cc">...<span class="message-cc">...</span></div>
<div class="msg-date">...<time class="message-date">Sun, 13 Apr 2025 14:30:00</time></div>
</div>
</header>
<div class="msg-body">...</div>
</article>
Attachments
<section class="attachments" data-count="2">
<span class="attachment-item" data-index="0" data-filename="report.pdf">
<a class="attachment-link" href="https://...">report.pdf</a> (1.2 MB)
</span>
</section>
Action buttons
Toolbar actions on the detail page use distinct CSS classes for targeting:
| Class | Action |
|---|---|
.action-reply | Reply link |
.action-forward | Forward link |
.action-delete | Delete button (form submit) |
.action-move | Move button (form submit) |
.action-toggle-read | Toggle read/unread (form submit) |
.action-toggle-star | Toggle starred flag (form submit) |
Autoconfig / Autodiscover
GET /.well-known/autoconfig/mail/config-v1.1.xml
Thunderbird autoconfig XML.
POST /autodiscover/autodiscover.xml
Microsoft Exchange autodiscover XML.
Complete Endpoint Table
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /auth/login | None | Authenticate, returns JWT |
| GET | /folders | User | List folders with counts |
| GET | /folders/{folder}/messages | User | List messages (offset, limit) |
| POST | /folders/{folder}/messages | User | Append raw RFC822 message |
| GET | /folders/{folder}/messages/{uid} | User | Fetch raw message |
| DELETE | /folders/{folder}/messages/{uid} | User | Delete message |
| PATCH | /folders/{folder}/messages/{uid}/flags | User | Replace flags |
| POST | /messages/send | User | Compose and send |
| GET | /aliases | User | List domain aliases |
| POST | /aliases | Admin | Add alias rule |
| DELETE | /aliases/{pattern} | Admin | Remove alias |
| GET | /admin/users | Admin | List users |
| POST | /admin/users | Admin | Create user |
| GET | /admin/users/{username} | Admin | Get user details |
| PATCH | /admin/users/{username} | Admin | Update user |
| DELETE | /admin/users/{username} | Admin | Delete user |
| PUT | /admin/users/{username}/password | Admin | Change password |
| PROPFIND | /caldav/{user}/ | Basic | User principal |
| PROPFIND | /caldav/{user}/calendars/ | Basic | Calendar home |
| PROPFIND | /caldav/{user}/calendars/{cal}/ | Basic | Calendar collection |
| GET | /caldav/{user}/calendars/{cal}/{event}.ics | Basic | Single event |
| REPORT | /caldav/{user}/calendars/{cal}/ | Basic | Calendar report |
| GET | /docs/{page} | None | Documentation |
| GET | /webmail/* | Cookie | Webmail interface |
| GET | /.well-known/autoconfig/mail/config-v1.1.xml | None | Thunderbird autoconfig |
| POST | /autodiscover/autodiscover.xml | None | Exchange autodiscover |
| OPTIONS | * | None | CORS preflight (204) |
Domain Configuration Reference
For each configured domain, clients should use the following settings:
| Protocol | Server | Port | Security |
|---|---|---|---|
| IMAP | https://mail.molodetz.nl:9003 | 993 | SSL/TLS |
| IMAP | https://mail.molodetz.nl:9003 | 143 | STARTTLS |
| SMTP (send) | https://mail.molodetz.nl:9003 | 587 | STARTTLS |
| SMTP (send) | https://mail.molodetz.nl:9003 | 465 | SSL/TLS |
| Exchange API | https://mail.molodetz.nl:9003 | ||
| CalDAV | https://mail.molodetz.nl:9003/caldav/{username}/calendars/ | ||
| Webmail | https://mail.molodetz.nl:9003/webmail/login | ||