That’s My Mail Docs/llms.txt
← All docs

Authentication

Every request (except GET /health) is authenticated with an HMAC-SHA256 signature over a canonical string. Nothing secret travels on the wire — only the signature.

Headers

Header Value
Authorization TMM-HMAC-SHA256 Credential=<keyId>, Signature=<hex>
X-TMM-Timestamp Unix time in milliseconds when the request was signed
X-TMM-Nonce A unique value per request (UUID), for replay protection
Content-Type application/json (for requests with a body)

The timestamp must be within ±5 minutes of server time, or the request is rejected (unauthorized). Each nonce is single-use within that window.

The string to sign

Build the canonical string by joining these five lines with \n:

METHOD            # uppercase, e.g. POST
PATH              # exact request target, INCLUDING the query string — e.g. /console/v1/projects/x/messages?limit=3
TIMESTAMP         # same value as X-TMM-Timestamp
NONCE             # same value as X-TMM-Nonce
sha256hex(body)   # hex SHA-256 of the raw JSON body ("" if no body)

Then Signature = hex(HMAC_SHA256(secret, stringToSign)).

Reference (Node.js)

import { createHmac, createHash, randomUUID } from "node:crypto";

function sign(secret: string, method: string, path: string, body = "") {
  const timestamp = Date.now();
  const nonce = randomUUID();
  const bodyHash = createHash("sha256").update(body).digest("hex");
  const stringToSign = [method.toUpperCase(), path, String(timestamp), nonce, bodyHash].join("\n");
  const signature = createHmac("sha256", secret).update(stringToSign).digest("hex");
  return { timestamp, nonce, signature };
}

You normally never write this yourself — the SDK does it for you.

Credential types

There are four kinds of credential. Which one you use depends on the endpoint.

Project key — tmm_<hex>

The everyday credential. Belongs to one project, carries scopes, and is minted via the API (and rotatable). Used for sending (/v1/services/sendEmail), message reads (/v1/messages/*), and the project's own console endpoints. The keyId (tmm_…) is public; the secret is shown once at mint time.

Scopes:

Scope Grants
messages:send POST /v1/services/sendEmail
messages:read GET /v1/messages/*
suppressions:read read the project's suppression list
suppressions:write add/remove suppressions
project:admin manage the project itself (keys, sender domain, webhooks)

Account token — tmma_<hex>

A user-scoped credential that drives organization + project lifecycle (/console/v1/organizations, /console/v1/projects) and operates on the projects of the orgs you belong to. Issued at /auth/v1/login and /auth/v1/signup (returned alongside the user), and self-manageable via /console/v1/account-tokens (mint/list/revoke your own); rotatable. This is the credential a logged-in app (e.g. the Console) acts with — never the god-key. Access is organization membership: you can reach a project iff you're a member of its organization (non-members get 404). Call POST /auth/v1/logout to revoke the token you're signing with (the Console does this on logout).

Organization token — tmmo_<hex>

An organization-scoped admin credential: it authenticates AS the organization and authorizes managing the org + its projects/domains/billing. Minted by default when an org is created (POST /console/v1/organizations returns it, secret once) and rotatable via /console/v1/organizations/{id}/tokens. It does not send mail — that stays a project key (tmm_, messages:send). Use it for org-scoped automation that shouldn't depend on a particular user's account token.

God-key (platform / bootstrap) — server-side only

The platform operator credential, accepted on /admin/v1/*. It is not a customer credential and must never ship in client code. It's sent as:

Authorization: TMM-HMAC-SHA256 Credential=admin, Signature=bootstrap
X-TMM-Admin-Key: <the platform admin secret>

Reserved for break-glass / bootstrap (creating the first staff user, minting account tokens). The staff Admin console authenticates with a staff account token (tmma_) via the SDK — /admin/v1/* accepts either — so the god-key ships in no deployed app.

Getting a key