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--dbalready exists — used by the Docker entrypoint so a mounted volume persists fixtures across container restarts.--docgen-backend:live(default) orstub— 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 toclaude -pfor genuinely sector-specific, plausible prose/data. Requires theclaudeCLI onPATHand configured credentials.stub: fabricates plausible-shaped content with Faker only, no subprocess/network call. Used by this repo's own test suite somake teststays fast and offline — seetests/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 messageGET /threads/{id}— full thread (messages in send order)GET /labels— system + in-use labelsPOST /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 smallrequests-based client hitting this mock's HTTP API.env == "prod": a realgoogleapiclient.discovery.build("gmail", "v1", credentials=...)service. Credential loading is left as aNotImplementedErrorstub — 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 matchnewer_than:<N><h|d|m>,older_than:<N><h|d|m>— relative date windowlabel:<LABEL>— case-sensitive exact label matchis:unread— sugar forlabel:UNREADhas: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=stubin the container environment if that's not available (e.g. CI), or mount a/datavolume pre-seeded bymake generateon the host so--if-missingskips 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.send—drafts.createonly, 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 repomock-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-Metaheader insidepayload.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.createaccepts a simplified JSON body (to/subject/body_text/body_html/thread_id), not the real API's strict RFC822raw(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_TOKENis 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f4f8e4d8b96ca3b9015a262bc239cd72a9d6e3b3c2ec97ef659c3ba7b671b66
|
|
| MD5 |
1a8199b7d39d75c9274f06d3c3c8bfeb
|
|
| BLAKE2b-256 |
594b770881d66ea9ef7d27e6bfadb5ddd44a3c8fdcf719e783bddad4ca180c6a
|
File details
Details for the file mock_gmail_api-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mock_gmail_api-0.1.0-py3-none-any.whl
- Upload date:
- Size: 37.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
957c1c5eac7725eea51fabbbd06ee08357e340b592ca585332bf4811dcfec517
|
|
| MD5 |
ea202d66de4fa11acb119fe056d73ffa
|
|
| BLAKE2b-256 |
bc7dadb8cc05f18000ecbf22466983d8f01440bbe1f2ed6ffd5f19ba7765f5fa
|