Documentation

Read the docs

Everything you need to run MailMock — from a single Docker command to the full stack with ClamAV and Rspamd.

Quick start

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:latest

Then

  • Point your application's SMTP client at localhost:1125 (no TLS, no auth).
  • Send some mail from your app.
  • Open the web UI at http://localhost:8080 to see captured messages.
Nothing is delivered anywhere — mail sent to MailMock is captured, parsed and displayed. It's a single-tenant developer tool meant to run in your own environment.

Sending & viewing mail

Any SMTP library works — set host and port, send, and the message is captured.

Sending mail

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=1125

Viewing messages

Open 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:

  • Plain — text body.
  • Attachments — downloadable files.
  • Headers — all MIME headers.
  • Source — the raw message exactly as it arrived (first 256 KiB, loaded on demand, shown as plain text).
  • View — the original HTML rendered in a sandboxed iframe (scripts disabled, remote resources blocked).
  • Analysis — the spam / spoofing / virus verdicts and the email client compatibility of the HTML.

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.

REST API

Base path /api. Responses are JSON. Interactive docs: Swagger UI at /swagger-ui.html.

MethodPathParametersReturns
GET/api/messagespage, sizePage of messages
GET/api/messages/searchsearch, page, sizeMatching messages
DELETE/api/messages/clear—Delete all
POST/api/messages/chaos/togglechaosEnabledToggle 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

Example

# 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 runs asynchronously, so immediately after a message arrives analysis may briefly be null before the verdicts are filled in.
An unknown message id (or attachment part) returns 404.
The 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.

Mail analysis

Every captured message is analyzed asynchronously across four dimensions, shown in the Analysis tab and the REST API.

Spam

LOWMEDIUMHIGH

A score 0–100 with a level and the reasons that contributed (spam keywords, ALL-CAPS, excessive links, sender/header anomalies).

Spoofing

PASSSUSPICIOUSFAIL

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.

Virus

CLEANSUSPICIOUSINFECTED

Attachment scanning for the standard EICAR test signature and risky file extensions.

Email client compatibility

PASSSUSPICIOUSFAIL

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.

What you see

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.

Built-in vs external engines

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.

Configuration

MailMock is configured entirely through environment variables, with sensible defaults.

Ports & server

VariableDefaultDescription
APP_PORT8080HTTP port for the web UI and REST API.
SMTP_PORT1125Port the SMTP server listens on.
MAX_MESSAGE_SIZE10MBMax 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.
An invalid 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.
With in-memory storage the worst-case memory is roughly 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.

Storage

VariableDefaultDescription
DB_ENGINEinMemoryStorage back-end: inMemory or mongo.
MESSAGES_LIMIT500Max stored messages; oldest evicted past the limit.
MONGO_HOSTlocalhostMongoDB host (when DB_ENGINE=mongo).
MONGO_PORT27017MongoDB port.
MONGO_DB / MONGO_USER / MONGO_PASSsmtpMongoDB database, username and password.

Chaos mode

VariableDefaultDescription
CHAOS_MODE_ENABLEDtrueEnable the ChaosEngine (random SMTP errors).
CHAOS_MODE_LATENCY5Inject an error roughly every Nth connection.

Analysis

VariableDefaultDescription
ANALYSIS_THREADS4Thread-pool size for asynchronous analysis.
ANALYSIS_SPAM_ENABLEDtrueBuilt-in heuristic spam scoring.
ANALYSIS_SPOOFING_ENABLEDtrueBuilt-in header-based spoofing detection.
ANALYSIS_VIRUS_ENABLEDtrueBuilt-in virus checks (EICAR + risky extensions).
ANALYSIS_HTMLCOMPAT_ENABLEDtrueEmail client compatibility check of the HTML part.
ANALYSIS_HTMLCOMPAT_CLIENTS7 main clientsClient families to check against (see the note below).
ANALYSIS_CLAMAV_ENABLEDfalseUse ClamAV for virus scanning when reachable.
ANALYSIS_SPAM_RSPAMD_ENABLEDfalseUse Rspamd for spam scoring when reachable.
External engines are off by default. ClamAV/Rspamd also accept _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.

Self-hosting

MailMock ships as a Docker image. Run a single container, or the full stack with real ClamAV and Rspamd.

Single container (recommended)

docker run -d --name mailmock \ --restart unless-stopped \ -p 8080:8080 -p 1125:1125 \ -e DB_ENGINE=inMemory \ justmediapl/mailmock:latest

Web UI on http://localhost:8080, SMTP on localhost:1125. In-memory storage, built-in analyzers enabled, a few hundred MB of RAM.

Persistence with MongoDB

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:latest

Full stack with ClamAV + Rspamd

The repository includes docker/docker-compose.yml, which runs MailMock together with ClamAV, Rspamd and MongoDB:

docker compose -f docker/docker-compose.yml up --build
ClamAV loads its signature database into memory, so the full stack needs roughly 2–3 GB of RAM. On first start it downloads the database (a minute or two) before it becomes healthy.

Security model

MailMock 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.