Skip to main content
Glama

🚌 MockPost

Your app thinks it's talking to Stripe, Telegram, Twilio and Gmail… but it's MockPost.

demo

MockPost is a local, multi-channel message emulator for end-to-end testing. Point your app's outbound credentials at MockPost and it captures every email, Telegram message, WhatsApp message, SMS, push notification, Slack/Discord webhook, Stripe event and OAuth session — while simulating realistic inbound events back to your app. All local, all deterministic, zero real credentials.

  • Web panel — a unified timeline of every message across all channels, with per-channel detail, per-app filtering and a "Copy as Markdown" button.

  • MCP server — an agent (Claude Code, OpenCode, Cursor, any MCP client) inspects the timeline, reads OTP codes and fires simulated events, all in one call.

  • Zero config faking — your app keeps its real SDKs and its real-looking credentials; MockPost identifies each app by those credentials, so it never knows it's being faked.

Why? Real services mean accidental sends, third-party credentials, and agents that can't inspect what your app actually sent. MockPost makes message delivery observable.

Screenshots

Timeline Telegram channel Apps


Related MCP server: TempMail OTP MCP Server

Quick start

PyPI (once published)

pipx install mockpost        # or: uv tool install mockpost
mockpost                     # starts the server on :8090
# MCP server, for any harness:
pipx install mockpost && mockpost-mcp   # stdio MCP server
docker run -p 8090:8090 -p 1025:1025 -v mockpost-data:/app/data ghcr.io/reiarseni/mockpost
# panel:      http://localhost:8090
# SMTP:       localhost:1025 (no auth)

From source

git clone https://github.com/reiarseni/mockpost
cd mockpost
uv venv && uv sync
uv run mockpost

60-second smoke test:

# send an email to your own machine
python -c "import smtplib; from email.mime.text import MIMEText; m=MIMEText('Your code is 123456'); m['Subject']='Verify'; m['From']='app@test.com'; m['To']='you@test.com'; smtplib.SMTP('localhost',1025).send_message(m)"

Then open http://localhost:8090 — the email is on the timeline, and get_latest_otp will find the code 123456.


What your app should point where

Channel

Point your app to

Typical env var

Email (SMTP)

localhost:1025 — AUTH PLAIN/LOGIN advertised, any credential accepted

SMTP_HOST=localhost, SMTP_PORT=1025

Telegram

http://localhost:8090/telegram (bot token goes in the URL: /bot<TOKEN>/...) — JSON, form-urlencoded or multipart, GET or POST

TELEGRAM_API_BASE_URL

WhatsApp

http://localhost:8090/whatsapp/v21.0 (any Graph version works)

WHATSAPP_GRAPH_BASE_URL

Web Push

POST /webpush/subscribe returns a browser-shaped subscription; send the encrypted push to its endpoint as pywebpush/web-push do

VAPID_PUBLIC_KEY from /config

SMS (Twilio)

http://localhost:8090/twilio/2010-04-01/Accounts/AC... (any SID works)

TWILIO_BASE_URL

FCM

http://localhost:8090/fcm/v1/projects/{id}/messages:send

FCM_ENDPOINT

APNs

http://localhost:8090/apns/3/device/{token}

APNS_ENDPOINT

Slack / Discord

http://localhost:8090/slack/webhook/{id} / .../discord/webhook/{id}

SLACK_WEBHOOK_URL

Stripe

API http://localhost:8090/stripe/v1, webhook secret whsec_... from /config

STRIPE_API_BASE, STRIPE_WEBHOOK_SECRET

OAuth (Google/GitHub/Facebook/X)

authorize/token/userinfo under http://localhost:8090/oauth/... (any client_id/secret works)

OAuth URLs

GitHub webhooks

create hook http://localhost:8090/github/repos/{owner}/{repo}/hooks, simulate .../github/simulate

X-GitHub-Event + X-Hub-Signature-256 (HMAC-SHA256, your secret)

Facebook webhooks

verify http://localhost:8090/facebook/webhook?hub.mode=subscribe&hub.verify_token=..., simulate .../facebook/simulate

hub.* challenge + X-Hub-Signature-256 (HMAC-SHA256, app secret)

X (Twitter) webhooks

register http://localhost:8090/x/webhook, CRC .../x/webhook/crc, simulate .../x/simulate

CRC response_token + X-Twitter-Webhooks-Signature (base64 HMAC-SHA256)

Google OAuth extras

tokeninfo + revoke under http://localhost:8090/oauth/google/...

real Google OAuth endpoints (no webhooks by design)

FTP / FTPS / SFTP

localhost:2121 (FTP), :2122 (FTPS explicit), :2123 (FTPS implicit), :2222 (SFTP) — log in with a registered app's name as the username, any password

MOCKPOST_FTP_PORT, MOCKPOST_FTPS_PORT, MOCKPOST_FTPS_IMPLICIT_PORT, MOCKPOST_SFTP_PORT

The /config page shows exact values plus copy-paste snippets per channel.

Inbound webhooks (back to your app): register your app's webhook in the panel (/webhooks) or via MCP register_webhook. Simulated inbound messages, status changes and signed Stripe events are POSTed to your app's URL — and every delivery (including the app's response body, even when it was down) is recorded in the timeline.


MCP integration

MockPost exposes an MCP server over stdio. Any harness that speaks MCP gets tools to inspect the timeline, read OTPs, and fire simulated events — so your agent can verify end-to-end behavior autonomously.

Prerequisite: the MockPost HTTP service must be running (default http://localhost:8090). The MCP server is a client of its internal API, so every tool call needs it up.

The MCP server ships with the repo and runs with python -m mcp_server.server (requires the Python deps from requirements.txt; MOCKPOST_URL defaults to http://localhost:8090).

Claude Code

Add to your project's .mcp.json (project-scoped) or ~/.claude.json / claude_desktop_config.json (user-scoped):

{
  "mcpServers": {
    "mockpost": {
      "command": "python",
      "args": ["-m", "mcp_server.server"],
      "env": { "MOCKPOST_URL": "http://localhost:8090" }
    }
  }
}

OpenCode

OpenCode supports MCP servers through its config (opencode.json / opencode.jsonc). Add the server under the mcp section:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "mockpost": {
      "type": "local",
      "command": ["python", "-m", "mcp_server.server"],
      "environment": { "MOCKPOST_URL": "http://localhost:8090" },
      "enabled": true
    }
  }
}

Some OpenCode versions use "type": "stdio" instead of "type": "local" — use whichever your installed version accepts.

Any MCP harness (Cursor, Windsurf, VS Code, Continue, custom clients)

The universal contract is the same: run the server with command + args, pass env vars, done.

  • VS Code (GitHub Copilot / MCP): mcp add mockpost -e "python" "-m" "mcp_server.server" or configure in .vscode/mcp.json / user settings under mcp.servers.

  • Cursor: Settings → MCP → Add server → type command, command python -m mcp_server.server.

  • Raw JSON (any stdio MCP client):

{
  "mcpServers": {
    "mockpost": {
      "command": "python",
      "args": ["-m", "mcp_server.server"],
      "env": { "MOCKPOST_URL": "http://localhost:8090" }
    }
  }
}

On Windows, wrap the command: "command": "cmd", "args": ["/c", "python", "-m", "mcp_server.server"].

What the agent can do

Tool

Purpose

get_timeline_markdown()

Call this first. Full picture of a test run across all channels, in Markdown.

list_sent_messages() / get_message_detail()

Inspect captured messages (full MIME/JSON payload).

simulate_incoming_message()

Simulate a user messaging your app (Telegram/WhatsApp) — delivered to your webhook.

simulate_delivery_webhook()

Fire delivered/read/failed status changes.

simulate_stripe_event()

Send a signed Stripe event (Stripe-Signature) to your webhook.

simulate_github_event() / simulate_facebook_event() / simulate_x_event()

Fire signed social webhooks (X-Hub-Signature-256 / X-Twitter-Webhooks-Signature).

verify_facebook_webhook()

Returns the hub.* verification URL used by Meta.

get_latest_otp() / generate_totp_secret() / get_totp_code()

Read OTP codes / TOTP for 2FA flows.

set_oauth_fake_profile() / get_oauth_session()

Drive fake OAuth logins and inspect the resulting session.

register_webhook() / trigger_webhook() / list_webhook_deliveries()

Manage and verify webhooks, including what the app returned (or that it was down).

create_push_subscription() / list_push_subscriptions() / expire_push_subscription()

Hand your app a real Web Push subscription, then kill it to test the 410 cleanup path.

simulate_push_token_unregistered()

Make an FCM or APNs token answer as uninstalled (404 / 410).

set_app() / set_test_id()

Scope everything to one app / one test run.

clear_channel() / clear_all()

Clean up between runs.

Typical agent flow:

1. set_app("my-app")                          # scope to your app
2. (your test runs and sends emails/messages)
3. get_timeline_markdown()                    # what did my app actually send?
4. get_latest_otp("user@test.com")            # the login code, parsed for you
5. simulate_incoming_message("whatsapp", ...) # now test the reply path
6. get_timeline_markdown()                    # verify the round trip

Per-app isolation (many apps testing at once)

Each app is identified by its fake credentials — it sends no extra headers, it never knows MockPost isn't the real service. Register the app once (panel /apps or MCP register_app):

Fake credential

Channel it identifies

Telegram bot token

telegram

Twilio Account SID

sms

Stripe sk_test_...

stripe

WhatsApp phone_id

whatsapp

FCM project_id

fcm

SMTP port (MOCKPOST_APPS='app-a:1025,...')

mail

Login username (FTP/FTPS/SFTP)

ftp / sftp

Consequences:

  • Captured messages are tagged with their app (panel filters by app).

  • A webhook bound to an app only receives that app's simulated events.

  • Unregistered credentials fall into the global queue.


Environment variables (all optional)

Variable

Default

MOCKPOST_HTTP_PORT

8090

MOCKPOST_SMTP_PORT

1025

MOCKPOST_HOST

127.0.0.1 (localhost only — no auth by design)

MOCKPOST_SMTP_HOST

127.0.0.1 (localhost only)

MOCKPOST_FTP_PORT

2121

MOCKPOST_FTPS_PORT

2122 (explicit AUTH TLS)

MOCKPOST_FTPS_IMPLICIT_PORT

2123

MOCKPOST_SFTP_PORT

2222

MOCKPOST_FTP_PASSIVE_PORTS

30000-30009 — map this range through Docker/NAT for passive FTP transfers

MOCKPOST_FTP_DATA_DIR / MOCKPOST_SFTP_DATA_DIR

./data/ftp / ./data/sftp

MOCKPOST_ALLOWED_WEBHOOK_HOSTS

(empty — all http(s) except metadata/link-local)

MOCKPOST_APPS

app-a:1025,app-b:1026 (per-app SMTP)

MOCKPOST_DB_PATH

./data/mockpost.db

MOCKPOST_URL

http://localhost:8090

MOCKPOST_STRIPE_WEBHOOK_SECRET

whsec_<random>

MOCKPOST_VAPID_CONTACT

mailto:mockpost@test.local

MOCKPOST_OAUTH_JWT_KEY

<random>

MOCKPOST_STRICT_AUTH

0 — set to 1 and every channel enforces its real authentication, answering the 401/403/535 the live API would

MOCKPOST_SMTP_USERNAME / MOCKPOST_SMTP_PASSWORD

(empty — any credential is accepted)


Emulated channels

Email (SMTP with AUTH, MailHog-style) · Telegram Bot API (13 methods, any body format) · WhatsApp Cloud API (any Graph version, messages + statuses) · Web Push (push service with real VAPID validation and payload decryption) · Twilio SMS (Messages.json + StatusCallback) · FCM · APNs · Slack · Discord · Stripe (checkout/payment_intents + signed Stripe-Signature events) · OTP (SMS/email/TOTP) · Fake OAuth2/OIDC for Google/GitHub/Facebook/X (JWT with local key) · Social webhooks (GitHub, Facebook, X) with per-provider signatures · FTP/FTPS/SFTP (per-app filesystem-backed roots, username-as-app-name login).

Protocol fidelity

The goal is that an app points its real SDK at MockPost and notices nothing: no adapters, no client changes. That means matching more than the happy path.

  • Transports and body formats. Telegram takes JSON, form-urlencoded, multipart or query parameters on GET and POST, like the live API; Slack and Discord also read the form-encoded payload field; OAuth token requests take form or JSON.

  • Status codes and bodies. Discord answers 204 with no body unless ?wait=true; APNs answers 200 with an empty body and the id in the apns-id header; Slack answers the literal ok.

  • Error envelopes. Telegram {ok, error_code, description}, Graph {error: {message, type, code, fbtrace_id}}, Google FCM {error: {code, message, status, details}}, Stripe {error: {message, type, code}}, Twilio {code, message, status}, OAuth {error, error_description}.

  • Identifier shapes. wamid.…, SM+32 hex, cs_test_…, pi_test_… with its client_secret, projects/{id}/messages/….

  • Failure paths worth testing. A dead Web Push subscription answers 410, an uninstalled FCM token 404 UNREGISTERED, an APNs one 410 Unregistered, and an oversized APNs payload 413 — the signals a backend uses to prune its own tables.

Set MOCKPOST_STRICT_AUTH=1 to also enforce authentication everywhere and get the real 401/403/535 when a client forgets its credentials.


License

MIT — see LICENSE.

Available Tools

31 tools
clear_allA

Delete all captured messages (or only those of a test_id/app).

Inputs: app, test_id.

clear_channelA

Delete all captured messages of a channel (cleanup between test runs). With app, only that app's messages.

Inputs: channel (required), app, test_id.

create_push_subscriptionA

Create a Web Push subscription and return it shaped exactly like the browser's PushSubscription (endpoint + keys.p256dh + keys.auth). Hand it to the app under test: MockPost keeps the private key, so when the backend sends the encrypted push with pywebpush or web-push it verifies the VAPID signature, decrypts the payload and captures the cleartext.

Inputs: no named parameters.

delete_appA

Delete a registered app. Its webhooks become global.

Inputs: app_id (required).

expire_push_subscriptionA

Mark a Web Push subscription as gone: the next push answers 410, which is how a backend learns it must delete a dead subscription.

Inputs: subscription_id (required).

generate_totp_secretB

Generate and store a TOTP secret (Google Authenticator). Returns base32 secret and otpauth:// URL.

Inputs: identifier (required).

get_channel_configB

Return host/port/base URL/fake tokens the app must use for that channel.

Inputs: channel (required).

get_latest_otpC

Return the latest OTP code (SMS or email) sent to an identifier.

Inputs: identifier (required), channel, test_id.

get_message_detailA

Return a message's full payload, including raw_payload (MIME/JSON).

Inputs: message_id (required).

get_oauth_sessionA

Inspect a simulated OAuth session: code, tokens, returned profile and expiry.

Inputs: session_id (required).

get_timeline_markdownA

FULL TIMELINE as Markdown: messages + webhook_events + stripe_events in chronological order. Call this first when verifying a test result; use get_message_detail only for a single event's detail. With app, only that app's events.

Inputs: app, limit, since, test_id, +1 more. Returns: result

get_totp_codeA

Compute the current TOTP code (6 digits) for a previously generated secret. Window ±1.

Inputs: identifier (required).

list_appsA

List registered apps with their fake credentials (MockPost identity).

Inputs: no named parameters. Returns: result

list_push_subscriptionsA

List Web Push subscriptions with their endpoint and status (active|gone).

Inputs: no named parameters. Returns: result

list_sent_messagesA

List captured messages for a channel (or all), newest first. channel: mail|telegram|whatsapp|webpush|sms|fcm|apns|slack|discord|stripe app: registered app name to filter only its messages.

Inputs: app, limit, status, channel, +1 more. Returns: result

list_webhook_deliveriesA

Webhook delivery history: what each app returned (code + body), even if it was down (response_code 0). Filterable by channel and app.

Inputs: app, limit, channel. Returns: result

list_webhooksA

List registered webhooks, optionally filtered by channel and app.

Inputs: app, channel. Returns: result

register_appA

Register or update an app: bind its FAKE credentials (Telegram bot token, Twilio SID, Stripe sk_test_, WhatsApp phone_id, FCM project_id) to a name. The app talks to MockPost using those credentials as if it were the real service; MockPost identifies it by them. Use the same name to update creds.

Inputs: name (required), creds.

register_webhookB

Register an app webhook (Telegram setWebhook, WhatsApp, Stripe...). If you pass an app (registered name), the webhook only receives events from that app.

Inputs: channel (required), target_url (required), app, token, +1 more.

set_appA

Set the active app (a name registered in MockPost): incoming event simulations and queries are filtered/delivered only to that app. Pass null/None for no app. Register apps with register_app.

Inputs: app (required). Returns: result

set_oauth_fake_profileB

Set the profile the fake OAuth provider (google|github|facebook|x) returns on the next login.

Inputs: profile (required), provider (required).

set_test_idA

Set the active test_id: all subsequent calls are filtered/persisted with it. Pass null/None to go back to the global queue.

Inputs: test_id (required). Returns: result

simulate_delivery_webhookA

Fire a delivery status webhook (delivered|read|failed) back to the app. Uses POST /whatsapp/simulate/status.

Inputs: status (required), message_id (required).

simulate_facebook_eventB

Fire a Facebook Graph API webhook event signed with X-Hub-Signature-256 to the app's registered Facebook webhook. app: registered app name to target; overrides set_app for this call.

Inputs: app, payload, event_type.

simulate_github_eventA

Fire a GitHub webhook event (push, issues, pull_request) signed with X-Hub-Signature-256 to the app's registered GitHub webhook. app: registered app name to target; overrides set_app for this call.

Inputs: app, payload, event_type.

simulate_incoming_messageB

Simulate an external user sending an inbound message to the app. channel: telegram (via /telegram/client/sendMessage) or whatsapp. app: registered app name to target; overrides set_app for this call.

Inputs: text (required), sender (required), channel (required), app, +1 more.

simulate_push_token_unregisteredA

Make a native push token look uninstalled: FCM answers 404 UNREGISTERED and APNs answers 410 Unregistered on the next send. channel: fcm | apns

Inputs: token (required), channel (required).

simulate_stripe_eventA

Build and send a simulated Stripe event, signed (Stripe-Signature), to the registered webhook. app: registered app name to target; overrides set_app for this call.

Inputs: event_type (required), app, overrides.

simulate_x_eventB

Fire an X (Twitter) Account Activity webhook event signed with X-Twitter-Webhooks-Signature to the app's registered X webhook. payload is a list of event objects, as the real API delivers. app: registered app name to target; overrides set_app for this call.

Inputs: app, payload, event_type.

trigger_webhookA

Fire a test event to a registered webhook and return the app's response.

Inputs: webhook_id (required), payload, event_type.

verify_facebook_webhookA

Verify a Facebook webhook subscription (hub.mode/hub.verify_token/ hub.challenge) as Meta does on registration. Returns the challenge URL.

Inputs: no named parameters. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 31 tools

    Disambiguation4/5

    Most tools have a distinct resource+action purpose, so an agent can usually select the right one. A few pairs overlap or look similar—list_webhooks vs list_webhook_deliveries, list_sent_messages vs get_timeline_markdown, and clear_channel vs clear_all—but the descriptions clarify the boundaries.

    Naming Consistency5/5

    All tool names use snake_case with a verb-first pattern such as list_, get_, set_, register_, simulate_, and clear_. Minor multiword names like simulate_push_token_unregistered remain readable and do not break the overall convention.

    Tool Count2/5

    31 tools is above the 25-tool threshold where the surface starts to feel heavy. The broad multi-channel domain justifies some size, but many simulate_* and channel-specific tools could be grouped or split into focused sub-servers to reduce cognitive load.

    Completeness4/5

    The tool set covers the core lifecycle well: app registration, webhooks, captured messages, OTP/TOTP, push subscriptions, OAuth sessions, simulation, and cleanup. Notable gaps exist, such as no delete/unregister webhook and limited inbound simulation channels beyond Telegram/WhatsApp, but typical verification workflows are still supported.

    Maintenance

    ActivityInactive
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Programmatic email deliverability testing for AI agents. Create inbox placement tests across Gmail, Outlook, Yahoo, Mail.ru, Yandex — get per-provider placement (Inbox/Spam/Promotions), SPF/DKIM/DMARC auth, Rspamd & SpamAssassin verdicts, DNS health (MX, PTR, DNSBL), and live SSE results.
      5
      74 npm
      2
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to create and use temporary email and SMS inboxes, check messages, and wait for or extract OTPs and verification links during account registration. Combines both channels through a unified OTP tool while redacting sensitive tokens in audit logs.
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Enables AI coding agents to create disposable test inboxes, wait for and read incoming email, and extract confirmation links and one-time codes for sign-up, password-reset, and magic-link flows.
      6
      86 npm
      MIT