Everything you need to run MailMock — from a single Docker command to the full stack with ClamAV and Rspamd.
Point your app's outgoing mail at MailMock and inspect every captured message in the browser.
# run MailMock (in-memory storage, built-in analyzers)
docker run -d --name mailmock \
-p 8080:8080 \
-p 1125:1125 \
justmediapl/mailmock:latestlocalhost:1125 (no TLS, no auth).http://localhost:8080 to see captured messages.Any SMTP library works — set host and port, send, and the message is captured.
Point your SMTP client at MailMock's SMTP port (default 1125). No TLS and no authentication. For a Spring Boot app, for example:
spring.mail.host=localhost
spring.mail.port=1125Open the web UI (default http://localhost:8080). The home page lists captured messages; use the search box to filter by subject, sender, recipient or body. Click a message to open it — with tabs:
Download .eml saves the original message as a file you can open in any mail client. Delete a single message, or use Clear all. New mail is pushed over WebSocket (SockJS/STOMP, endpoint /ws, topic /topic/messages) — the UI shows a toast and refreshes automatically.
Base path /api. Responses are JSON. Interactive docs: Swagger UI at /swagger-ui.html.
| Method | Path | Parameters | Returns |
|---|---|---|---|
GET | /api/messages | page, size | Page of messages |
GET | /api/messages/search | search, page, size | Matching messages |
DELETE | /api/messages/clear | — | Delete all |
POST | /api/messages/chaos/toggle | chaosEnabled | Toggle chaos mode |
GET | /api/messages/chaos | — | Current chaos state |
GET | /api/message/{id} | — | Single message + analysis |
PUT | /api/message/{id}/read | — | Mark read |
PUT | /api/message/{id}/unread | — | Mark unread |
GET | /api/message/{id}/{partId} | — | Attachment bytes; the file name comes from Content-Disposition (attachment-<partId> when the sender gave none) |
GET | /api/message/{id}/eml | — | The original message as an .eml download (message/rfc822) |
GET | /api/message/{id}/source | — | First 256 KiB of the original message as plain text; header X-Source-Truncated says whether it was cut |
DELETE | /api/message/{id} | — | Delete one |
# list the latest messages
curl "http://localhost:8080/api/messages?page=0&size=10"
# search
curl "http://localhost:8080/api/messages/search?search=invoice"
# download the original message as an .eml file; -OJ names it after the subject
# (for curl, characters other than letters, digits, space and . _ - ( ) [ ] + , @ # ~ ! $ & ' become "_"; browsers get the full name)
curl -OJ "http://localhost:8080/api/message/<id>/eml"
# turn chaos mode off
curl -X POST "http://localhost:8080/api/messages/chaos/toggle?chaosEnabled=false"analysis may briefly be null before the verdicts are filled in.404.analysis object also carries htmlCompat (status, issues, featuresChecked, truncated, datasetDate, note); its status is NOT_RUN for a message without an HTML part. A client that parses the response with a strict type must accept the new field.Every captured message is analyzed asynchronously across four dimensions, shown in the Analysis tab and the REST API.
A score 0–100 with a level and the reasons that contributed (spam keywords, ALL-CAPS, excessive links, sender/header anomalies).
From header heuristics: From vs Return-Path / envelope mismatch, display-name spoofing, Authentication-Results failures, punycode look-alike domains and From vs Reply-To divergence.
Attachment scanning for the standard EICAR test signature and risky file extensions.
Like the HTML Check in Mailpit: the HTML part is compared with the bundled Can I email data (MIT, works offline). MailMock finds the HTML elements, attributes and CSS properties (from <style> blocks and style="" attributes) that Gmail, Outlook, Apple Mail, Yahoo! Mail, Thunderbird, Samsung Email or ProtonMail do not support, and lists each one with the affected clients and a link to its caniemail.com page. Plain-text messages show NOT_RUN.
The status counts only features unsupported in two or more client families: PASS = none, SUSPICIOUS = 1–2, FAIL = 3 or more. Four features that nearly every email uses (<body>, the target attribute, # anchor links and the color-scheme meta tag) are listed but not counted. Features missing in one family, partially supported ones, and at-rules or pseudo-selectors (@media, :hover) are listed but do not change the status.
In the Analysis tab the result sits under Email client compatibility: the status, the number of features found, and groups of findings — Not supported in several client families, Not supported in one client family, Partially supported and Present, effect unknown (at-rules, pseudo-selectors). The first two are open by default. Each finding shows the feature name (a link to its caniemail.com page), a html / css badge, how many times it occurs, and the affected clients with their platforms, marked no or partial. A footer gives the date of the bundled dataset. In the message list a </> icon marks messages with status SUSPICIOUS or FAIL.
Limits: only the first 200,000 characters of the HTML are analyzed (truncated is then true) and at most 100 findings are returned; note says when either applies. 248 of the 308 features in the dataset can be detected.
"htmlCompat": {
"status": "SUSPICIOUS",
"featuresChecked": 12,
"truncated": false,
"datasetDate": "2026-09-16",
"issues": [{
"title": "box-shadow", "category": "css", "severity": "BROAD", "found": 1,
"url": "https://www.caniemail.com/features/css-box-shadow/",
"impacts": [{ "familyName": "Gmail", "support": "NO", "platforms": ["Desktop Webmail", "Mobile Webmail"] }, ...]
}, ...]
}Not checked: images, CSS units, functions and selectors, gradients, colour functions, CSS values other than display: flex | grid | none and !important, the doctype and comments, and external stylesheets (never fetched). It is a heuristic based on Can I email data: it does not validate your HTML or render it, and markup inside <!--[if mso]> comments is not analyzed.
The built-in analyzers are heuristic and run in-process with no dependencies — the Analysis tab is always populated. You can additionally enable real ClamAV (virus) and Rspamd (spam). When an external engine is enabled and reachable, its verdict replaces the built-in one; if it's unreachable, MailMock falls back to the built-in result.
MailMock is configured entirely through environment variables, with sensible defaults.
| Variable | Default | Description |
|---|---|---|
APP_PORT | 8080 | HTTP port for the web UI and REST API. |
SMTP_PORT | 1125 | Port the SMTP server listens on. |
MAX_MESSAGE_SIZE | 10MB | Max size of a single message (headers + body). Accepts a unit (10MB, 512KB) or plain bytes. A larger message is rejected with 552. |
PROFILES | — | Spring profiles to activate. |
MAX_MESSAGE_SIZE (not a size, zero, negative, or 2 GB or more) stops the application at startup with an error that names the variable. With DB_ENGINE=mongo a message is stored as one document holding both the raw message and its extracted text, and MongoDB caps a document at 16 MB, so the practical limit is about 8 MB for text-heavy mail; MailMock logs a warning at startup above that.MESSAGES_LIMIT × MAX_MESSAGE_SIZE, plus the copies held while a message is being received. For example 500 messages of 5 MB need about 2.5 GB, so with large messages lower MESSAGES_LIMIT or MAX_MESSAGE_SIZE, or raise the JVM heap (for instance JAVA_TOOL_OPTIONS=-Xmx2g). The limit holds under concurrent sends: however many clients deliver at once, the store never keeps more than MESSAGES_LIMIT messages.| Variable | Default | Description |
|---|---|---|
DB_ENGINE | inMemory | Storage back-end: inMemory or mongo. |
MESSAGES_LIMIT | 500 | Max stored messages; oldest evicted past the limit. |
MONGO_HOST | localhost | MongoDB host (when DB_ENGINE=mongo). |
MONGO_PORT | 27017 | MongoDB port. |
MONGO_DB / MONGO_USER / MONGO_PASS | smtp | MongoDB database, username and password. |
| Variable | Default | Description |
|---|---|---|
CHAOS_MODE_ENABLED | true | Enable the ChaosEngine (random SMTP errors). |
CHAOS_MODE_LATENCY | 5 | Inject an error roughly every Nth connection. |
| Variable | Default | Description |
|---|---|---|
ANALYSIS_THREADS | 4 | Thread-pool size for asynchronous analysis. |
ANALYSIS_SPAM_ENABLED | true | Built-in heuristic spam scoring. |
ANALYSIS_SPOOFING_ENABLED | true | Built-in header-based spoofing detection. |
ANALYSIS_VIRUS_ENABLED | true | Built-in virus checks (EICAR + risky extensions). |
ANALYSIS_HTMLCOMPAT_ENABLED | true | Email client compatibility check of the HTML part. |
ANALYSIS_HTMLCOMPAT_CLIENTS | 7 main clients | Client families to check against (see the note below). |
ANALYSIS_CLAMAV_ENABLED | false | Use ClamAV for virus scanning when reachable. |
ANALYSIS_SPAM_RSPAMD_ENABLED | false | Use Rspamd for spam scoring when reachable. |
_HOST, _PORT, _URL and _TIMEOUT variables — see Self-hosting.ANALYSIS_HTMLCOMPAT_CLIENTS is a comma-separated list of client families. The default is gmail,outlook,apple-mail,yahoo,thunderbird,samsung-email,protonmail; other ids from Can I email include aol, gmx, fastmail, hey and web-de. Unknown ids are ignored with a warning. A status other than PASS needs at least two families, and an empty list gives NOT_RUN.MailMock ships as a Docker image. Run a single container, or the full stack with real ClamAV and Rspamd.
docker run -d --name mailmock \
--restart unless-stopped \
-p 8080:8080 -p 1125:1125 \
-e DB_ENGINE=inMemory \
justmediapl/mailmock:latestWeb UI on http://localhost:8080, SMTP on localhost:1125. In-memory storage, built-in analyzers enabled, a few hundred MB of RAM.
docker run -d --name mailmock \
-p 8080:8080 -p 1125:1125 \
-e DB_ENGINE=mongo \
-e MONGO_HOST=mongo -e MONGO_PORT=27017 \
-e MONGO_USER=smtp -e MONGO_PASS=smtp -e MONGO_DB=smtp \
justmediapl/mailmock:latestThe repository includes docker/docker-compose.yml, which runs MailMock together with ClamAV, Rspamd and MongoDB:
docker compose -f docker/docker-compose.yml up --buildMailMock is a single-tenant developer tool with no authentication by design (like MailHog/Mailpit) — anyone who can reach the web UI or SMTP port can read and delete captured mail. Don't expose it to untrusted networks; if you must, put it behind your own reverse proxy. Captured HTML is always rendered in a sandboxed iframe, so untrusted mail cannot execute scripts in your browser.