Skip to main content

Mock Gmail REST API server + seeded synthetic email fixture generator

Project description

mock-gmail-api

This is a public-source project — see CONTRIBUTING.md for the PR policy.

A mock Gmail REST API server, plus a seeded synthetic data generator, for developing and testing your own Python code entirely against synthetic data. It mimics real Gmail's /gmail/v1/users/me/... paths and JSON shapes closely enough that calling code written against this mock runs unmodified against the real Gmail API — the only change is which client gets constructed, at a single ENV=dev/ENV=prod branch point (see Calling it from Python). It exists so a Stage-0 pitch-deck-inbound routing feature (or similar future tear-sheet-draft features) can be built and tested on a dev server with zero real Gmail credentials, then run unmodified on your own prod server.

Quickstart

git clone <this-repo>
cd mock-gmail-api
uv sync
make generate
make serve

Then, in another shell:

curl -H "Authorization: Bearer dev-secret-token" \
  "http://localhost:8000/gmail/v1/users/me/messages?q=to:pitches@acme.example"

Install

git clone <this-repo>
cd mock-gmail-api
uv sync

Requires Python 3.13+ and uv.

Generating synthetic data

make generate
# equivalent to:
uv run mock-gmail-api generate --seed 42 --volume 200 --pitch-ratio 0.3 --db ./fixtures.sqlite
  • --seed: Faker's RNG seed. The same seed always produces a byte-identical database — message dates are computed relative to a fixed reference timestamp baked into the generator, not wall-clock time, so regenerating on a different day doesn't change the output.
  • --volume: approximate total number of messages generated.
  • --pitch-ratio: fraction of generated threads that are VC pitch-inbound mail (company/sector/ask/stage) rather than filler (newsletters, internal mail, calendar invites, promos).
  • --if-missing: skip regeneration if --db already exists — used by the Docker entrypoint so a mounted volume persists fixtures across container restarts.
  • --docgen-backend: live (default) or stub — see Attachment content generation.
  • --docgen-cache-dir: where generated attachment content is cached (default .docgen_cache/, gitignored).

A sample fixtures.sqlite (seed 42, volume 200, generated with --docgen-backend stub to avoid a live claude -p bill for a repo asset) is committed to this repo so make serve works with zero setup. Regenerate it with the live backend (make generate, no --docgen-backend flag) if you want genuinely realistic attachment content.

Attachment content generation

This is mock-only, dev-fixture code. It has no relationship to the real Gmail API. See src/mock_gmail_api/docgen/__init__.py's module docstring.

Pitch-inbound attachments (_Pitch_Deck.pdf, _Cap_Table.xlsx, _One_Pager.docx) carry real, structurally valid pdf/docx/xlsx content — company-specific pitch decks, cap tables, and one-pager memos — instead of MOCKATTACHMENTDATA filler bytes. This exists so a downstream document classifier (one that ports bronze artifacts to markdown) has something non-trivial to actually summarize when developed against this mock.

Two content backends, selected by --docgen-backend / MOCK_GMAIL_DOCGEN_BACKEND:

  • live (default): shells out to claude -p for genuinely sector-specific, plausible prose/data. Requires the claude CLI on PATH and configured credentials.
  • stub: fabricates plausible-shaped content with Faker only, no subprocess/network call. Used by this repo's own test suite so make test stays fast and offline — see tests/conftest.py.

Generated content is cached to disk (.docgen_cache/ by default, keyed by a hash of doc_type + company/sector/stage/ask) so re-running generate with the same seed reuses previously generated bytes rather than re-invoking claude -p on every run — this is also what makes --seed's determinism guarantee hold for attachments, not just message rows.

Running the server

make serve
# equivalent to:
uv run mock-gmail-api serve --db ./fixtures.sqlite

Serves on http://localhost:8000 by default. Set MOCK_GMAIL_DEV_TOKEN to require a specific bearer token (see Auth below).

Endpoint reference

All endpoints below are under /gmail/v1/users/me/, mirroring real Gmail's per-account REST paths. GET /health is separate and unauthenticated.

  • GET /messages — search/list (q=, pageToken=, maxResults=)
  • GET /messages/{id} — full message
  • GET /threads/{id} — full thread (messages in send order)
  • GET /labels — system + in-use labels
  • POST /drafts — create a draft (never send)
  • GET /messages/{id}/attachments/{attachmentId} — generated attachment bytes (see Attachment content generation)

Calling it from Python

Minimal requests example

import requests

resp = requests.get(
    "http://localhost:8000/gmail/v1/users/me/messages",
    params={"q": "to:pitches@acme.example newer_than:1d"},
    headers={"Authorization": "Bearer dev-secret-token"},
    timeout=10,
)
resp.raise_for_status()
print(resp.json())

The ENV=dev/ENV=prod pattern

examples/dev_prod_client.py is a worked, copy-pasteable template for a consuming project to drop into its own repo — this repo does not import it. It exposes get_gmail_client(env), returning an object with identically-shaped search_messages/get_thread/create_draft methods in both branches:

  • env == "dev": a small requests-based client hitting this mock's HTTP API.
  • env == "prod": a real googleapiclient.discovery.build("gmail", "v1", credentials=...) service. Credential loading is left as a NotImplementedError stub — that OAuth/token-refresh plumbing is the consuming project's own responsibility on the prod server, per the trust-boundary model this repo exists to support. This repo's own tests never exercise the prod branch.

Run it against a local mock server with:

ENV=dev MOCK_GMAIL_BASE_URL=http://localhost:8000 \
  MOCK_GMAIL_DEV_TOKEN=dev-secret-token \
  uv run python examples/dev_prod_client.py

Search query grammar

q= supports a subset of real Gmail's search operators — enough to cover the Stage-0 skill query this mock was built for, to:pitches@acme.example newer_than:1d, plus a few more for testing:

  • to:<addr>, from:<addr>, subject:<text> — substring match
  • newer_than:<N><h|d|m>, older_than:<N><h|d|m> — relative date window
  • label:<LABEL> — case-sensitive exact label match
  • is:unread — sugar for label:UNREAD
  • has:attachment — messages with at least one attachment
  • anything else is treated as a free-text term matched against subject/from/body

Unknown or malformed operator tokens are silently folded into free-text (logged, not errored), matching real Gmail's lenient parser. No free-text body search beyond that free-text fallback — this is not a full-text index.

Fault injection

Ported from mock-superhuman-mcp's FaultProfile. Enable server-wide via CLI flags or env vars:

uv run mock-gmail-api serve --db ./fixtures.sqlite \
  --faults rate-limit,timeout --fault-chance 0.1
# or: MOCK_GMAIL_FAULTS=rate-limit,timeout MOCK_GMAIL_FAULT_CHANCE=0.1

Fault types: rate-limit (raises HTTP 429), timeout (sleeps 1-3s before responding), malformed (strips id from the response, adds __malformed__: true), duplicate-page (duplicates the first item in a messages list response). Each enabled fault independently has probability --fault-chance of firing per eligible request; at most one fault fires per request.

Override per-request with headers, regardless of server-wide config:

curl -H "X-Mock-Faults: rate-limit" -H "X-Mock-Fault-Chance: 1.0" ...

X-Mock-Faults: none disables faults for that request even if the server has faults enabled globally.

Docker

make docker-build
make docker-run

The image bakes in both the generator and server. Its entrypoint always runs generate --if-missing before serve, so the same image works whether or not a fixture volume is mounted — a fresh container without a volume gets a freshly generated DB; one with a persisted /data volume keeps its existing fixtures across restarts.

The entrypoint does not set MOCK_GMAIL_DOCGEN_BACKEND, so a fresh container defaults to the live backend and needs a working claude CLI

  • credentials to generate its first fixture DB. Set MOCK_GMAIL_DOCGEN_BACKEND=stub in the container environment if that's not available (e.g. CI), or mount a /data volume pre-seeded by make generate on the host so --if-missing skips generation entirely.

See docker-compose.example.yml for a documented standalone service block (env vars, named volume) to copy into your own compose setup — this repo does not ship its own compose stack.

Scope / Non-goals

This mock deliberately implements a narrow slice of the Gmail API:

  • No messages.senddrafts.create only, ever. This matches a common policy of never sending mail programmatically; the weekly tear-sheet feature only ever creates drafts.
  • No labels-write / messages.modify, no history/watch/push, no settings endpoints.
  • No real OAuth2 — see Auth.
  • Single-inbox (users/me) only — no multi-mailbox concept, matching real Gmail's per-account API surface (unlike sibling repo mock-superhuman-mcp, which is multi-mailbox by design).
  • Attachment bytes are real, generated pdf/docx/xlsx content (see Attachment content generation below) — fabricated, not real deal data, but structurally valid documents rather than filler bytes.
  • Pitch metadata (company/sector/ask/stage) is exposed only via the X-Pitch-Meta header inside payload.headers, never as a top-level JSON field — this is intentional: it forces real extraction logic to actually parse body/PDF text rather than shortcut past the thing Stage-0 is meant to exercise.
  • drafts.create accepts a simplified JSON body (to/subject/ body_text/body_html/thread_id), not the real API's strict RFC822 raw (base64url MIME) field.

Auth

Trivial bearer-token check, not real OAuth2 — there is no /token or /authorize endpoint. Every route except /health requires an Authorization: Bearer <token> header:

  • If MOCK_GMAIL_DEV_TOKEN is set, the token must match it exactly (401 otherwise).
  • If unset, any nonempty token is accepted.

Development

make lint   # ruff check + ruff format --check + pyright
make test   # pytest

Trust-boundary note

This repo is dev-only and holds zero real credentials — it exists purely to let a consuming project's Python code be developed and tested against synthetic data before running unmodified against real Gmail on that project's own prod server. This mirrors a common dev/prod split: a dev server (run by the tool builder, synthetic data only) and a prod server (run by the consuming project, real credentials, never touched by the tool builder). No Gmail OAuth flow, real client secret, or production data belongs in this repo.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mock_gmail_api-0.1.0.tar.gz (39.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mock_gmail_api-0.1.0-py3-none-any.whl (37.5 kB view details)

Uploaded Python 3

File details

Details for the file mock_gmail_api-0.1.0.tar.gz.

File metadata

  • Download URL: mock_gmail_api-0.1.0.tar.gz
  • Upload date:
  • Size: 39.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.4

File hashes

Hashes for mock_gmail_api-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2f4f8e4d8b96ca3b9015a262bc239cd72a9d6e3b3c2ec97ef659c3ba7b671b66
MD5 1a8199b7d39d75c9274f06d3c3c8bfeb
BLAKE2b-256 594b770881d66ea9ef7d27e6bfadb5ddd44a3c8fdcf719e783bddad4ca180c6a

See more details on using hashes here.

File details

Details for the file mock_gmail_api-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for mock_gmail_api-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 957c1c5eac7725eea51fabbbd06ee08357e340b592ca585332bf4811dcfec517
MD5 ea202d66de4fa11acb119fe056d73ffa
BLAKE2b-256 bc7dadb8cc05f18000ecbf22466983d8f01440bbe1f2ed6ffd5f19ba7765f5fa

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page