# Marmunk documentation 6 guides, full text. Start with /connect for agent setup. ## Queue an email without guessing what happened. Marmunk accepts a durable intent first. Provider submission and recipient delivery happen asynchronously and remain traceable. ### Before you send Create an API key with email.send and email.read. Your tenant must also be active, the sender domain must be approved, and the workload must fit its sending envelope. Permission is layered. A valid API key cannot bypass tenant state, domain approval, suppression, quota, or runtime safety policy. ### Submit a durable intent Every mutation requires a stable idempotency key. Reuse it only when the business operation and message semantics are identical. ``` curl https://api.marmunk.com/v1/emails \ --request POST \ --header "Authorization: Bearer $MARMUNK_API_KEY" \ --header "Idempotency-Key: order_8291_receipt" \ --header "Content-Type: application/json" \ --data '{ "from": { "email": "receipts@example.com", "name": "Example" }, "to": [{ "email": "customer@example.net" }], "subject": "Your receipt", "text": "Thanks for your order." }' ``` ### Keep both IDs ``` { "id": "msg_01example", "status": "queued", "created_at": "2026-07-29T20:00:00.000Z", "request_id": "req_01example" } ``` 202 Accepted means Marmunk durably recorded the send. It does not mean SES accepted it or the recipient received it. Store the msg_* value with your business record. ### Trace the lifecycle ``` curl https://api.marmunk.com/v1/email-traces/msg_01example \ --header "Authorization: Bearer $MARMUNK_API_KEY" ``` ## Give every caller only the authority it needs. Marmunk supports tenant-bound API keys, browser sessions, and delegated OAuth for MCP. Sending approval remains a separate control. ### API keys Use Bearer authentication for server-to-server API calls. Keys are shown once, strongly hashed at rest, tenant-bound, expiring, and revocable. ``` Authorization: Bearer mmk_key_... ``` Never put a Marmunk key in browser code. Keep it in your server-side secret store and redact it from logs and support artifacts. ### Core scopes email.sendSubmit approved email intents.email.readRead messages, delivery events, and safe traces.webhook.manageCreate endpoints and rotate signing secrets.suppression.manageManage customer-controlled recipient blocks.usage.readRead current limits and reserved usage. ### Agent access OAuth-capable MCP clients should connect to https://api.marmunk.com/mcp. In Cursor, install the Marmunk plugin or use the Add to Cursor link on the Cursor page (https://marmunk.com/cursor). Browserless runtimes use a separately created, expiring agent credential with an explicit scope set. Agent credentials cannot create accounts, accept legal terms, mint API keys, submit sending applications, or call operator routes. ## Retry the business action, not an uncertain network write. Marmunk binds each idempotency key to canonical send semantics within one tenant and operation. ### The contract An identical retry returns the original msg_* resource. The retry itself receives a new req_* because it is a new API attempt. Reusing the key with changed recipients, subject, content, sender, or attachments returns 409 idempotency_conflict. ### Choose stable business keys ``` receipt:v1:order_8291 password-reset:v1:reset_48f2 invite:v2:wedding_12:guest_93 ``` Keep keys deterministic, non-secret, and bounded. Do not generate a new UUID on each retry; doing so defeats idempotency. ### Treat unknown as a safety state Never blindly replay unknown. Marmunk uses this state when a provider write may have succeeded but the response was lost. Inspect the message trace and reconcile before another send. ## Consume immutable delivery facts exactly once. Marmunk normalizes provider events, signs the canonical request bytes, and retries webhook delivery with bounded backoff. ### Verify before parsing ``` Marmunk-Webhook-Id: whd_... Marmunk-Webhook-Timestamp: 1785369600 Marmunk-Webhook-Signature: v1= ``` Compute HMAC-SHA256 over timestamp + \".\" + raw_body. Use the raw request bytes, compare signatures in constant time, reject stale timestamps, then deduplicate by webhook ID. ### Delivery event ``` { "id": "evt_01example", "type": "delivery.succeeded", "created_at": "2026-07-29T20:01:20.000Z", "data": { "email_id": "msg_01example", "recipient_id": "rcp_01example" } } ``` ### Handler order - Read the raw body and signature headers. - Verify timestamp and HMAC. - Claim the event ID in durable storage. - Apply the normalized outcome transactionally. - Return a 2xx response only after the durable claim succeeds. ## Let agents operate the same contract humans use. The Marmunk MCP server is an authenticated adapter over the public API—not a privileged back door. ### One-click install The hosted Streamable HTTP endpoint is the product. Cursor and VS Code Copilot open a browser for signup and scope consent. There is no API key in the MCP config. Add to Cursor (cursor://anysphere.cursor-deeplink/mcp/install?name=marmunk&config=eyJ1cmwiOiJodHRwczovL2FwaS5tYXJtdW5rLmNvbS9tY3AifQ==)Add to VS Code (https://insiders.vscode.dev/redirect/mcp/install?name=marmunk&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapi.marmunk.com%2Fmcp%22%7D) In Cursor you can also run: ``` /add-plugin marmunk ``` Manual MCP entry: ``` { "mcpServers": { "marmunk": { "url": "https://api.marmunk.com/mcp" } } } ``` Signup is the OAuth loop. Connect opens Google sign-in, then Approve. First-time Approve creates a test-only account, names the organization from your email, and accepts Terms. Agents cannot create accounts through a tool. ### Hosted connection ``` https://api.marmunk.com/mcp ``` Use this URL directly in an OAuth-capable MCP client. Marmunk opens a browser for Google sign-in and explicit scope consent. First-time Approve creates the test-only account. Read access is selected by default; management and sending require separate approval. Production sending stays off; 10 emails a day until you add a card. ### Remote send debugging ``` Tool: marmunk_email_trace Input: { "reference_id": "msg_01example" } // A req_* reference traces one API attempt. // A msg_* reference traces the durable send lifecycle. ``` ### Same safety boundaries MCP calls carry a channel marker for audit and debugging, but authentication, tenant isolation, idempotency, policy, suppression, quota, and approval behave exactly like direct REST calls. ## Start with the message. Fall back to the request. A request ID identifies one API attempt. A message ID identifies the durable email across every asynchronous stage. ### Two IDs, two questions req_* ### What happened in this API call? Validation, policy denial, accepted request, or idempotent replay. Every retry gets a new request ID. msg_* ### What happened to this email? Queue state, provider attempts, recipient events, and webhook outcomes. Idempotent retries converge on one message ID. ### Use either reference ``` GET /v1/email-traces/msg_01example GET /v1/email-traces/req_01example ``` The response is tenant-scoped and support-safe. It includes request outcomes, message and recipient state, safe provider attempt metadata, normalized events, and webhook delivery status. It excludes bodies, attachments, credentials, raw provider payloads, and webhook response content. ### TypeScript SDK ``` const accepted = await client.sendEmail(message, { idempotencyKey: "receipt:v1:order_8291", }) const trace = await client.traceEmail(accepted.id) console.log(trace.requests, trace.provider_attempts, trace.events) ``` ### Triage by state queuedDurably accepted; waiting for provider submission.policy_heldStored but blocked until current policy permits submission.submittedThe provider accepted the submission.unknownProvider outcome is ambiguous. Investigate; do not retry blindly.bouncedDelivery failed for at least one recipient.complainedA recipient reported spam; suppression protects future sends.