Skip to content
Handwritten

For agents and developers

Let your agent design and send handwritten letters.

Your agent can use the handwriting fonts you make here: find the right one, draft a note, show you a preview, and mail it once you agree. Connect it through our MCP server, or call the REST API directly.

What an agent can do

MCP toolDoesCost
list_fontsYour fonts: id, name, how many charactersfree
get_fontOne font: characters, alternates, settings, attributionfree
list_libraryLibrary fonts with their attribution; their ids work for previews and sendsfree
preview_letterRender a letter or postcard as PDF, PNG or SVGfree
send_letterMail a handwritten letter$1.00 or 100 credits
send_postcardMail a handwritten postcard$1.65 or 165 credits
list_sendsYour sends, newest first, with statusfree
get_send_statusWhere a send is: queued, submitted, mailed…free
get_creditsBalance and recent ledgerfree

What keeps an agent in check

The MCP send tools instruct the agent to preview, show you the text, the address and the price, and wait for a clear yes. That is an instruction to the agent, not something our server can see or enforce. What the server does enforce: each API key can make at most 5 sends a day, it can only spend credits you already have, a card payment returns a checkout link you open yourself, and you can revoke a key at any time.

Credits work here too

Every send that is mailed earns 5 credits back. Making your first font in the studio earns 10 credits. See pricing.

Get an API key

  1. Sign in with a passkey and make at least one font.
  2. On your account page, create an API key and name it after the agent that will use it (“Claude Desktop”).
  3. Copy the key straight away. It starts with hw_ and is shown once.
  4. Revoke it on the same page whenever you like; the agent loses access immediately.

A key acts as you: it can read your fonts and spend your credits on sends, up to 5 sends a day per key. Give each agent its own.

MCP setup

The server runs on your machine over stdio (or as a small HTTP server) and forwards each call to the API with your key. It needs Node 20 or newer.

1. Build it

Terminal
git clone <this repo> handwritten && cd handwritten
npm install
npm run mcp:build

2a. Claude Code

Terminal
claude mcp add handwritten \
  --env HANDWRITTEN_API_KEY=hw_... \
  --env HANDWRITTEN_BASE_URL=https://your-handwritten-site \
  -- node /absolute/path/to/handwritten/mcp/dist/mcp/src/stdio.js

2b. Claude Desktop

claude_desktop_config.json
{
  "mcpServers": {
    "handwritten": {
      "command": "node",
      "args": [
        "/absolute/path/to/handwritten/mcp/dist/mcp/src/stdio.js"
      ],
      "env": {
        "HANDWRITTEN_API_KEY": "hw_...",
        "HANDWRITTEN_BASE_URL": "https://your-handwritten-site"
      }
    }
  }
}

Or over HTTP

Terminal
PORT=3333 HANDWRITTEN_BASE_URL=https://your-handwritten-site \
  npm run mcp:http

claude mcp add --transport http handwritten http://127.0.0.1:3333/mcp \
  --header "Authorization: Bearer hw_..."

The HTTP server holds no key; each client sends its own in the Authorization header.

Then ask: “Send my sister a postcard in my handwriting saying congratulations on the new flat.”

REST quickstart

Everything lives under /api/v1, speaks JSON, and takes your key as Authorization: Bearer. Stroke coordinates are em units from 0 to 1000 with y pointing down; money is in cents; times are ISO 8601 UTC.

Set up once
export HANDWRITTEN_BASE_URL=https://your-handwritten-site
export HANDWRITTEN_API_KEY=hw_...
Every error looks like this
{
  "error": {
    "code": "insufficient_credits",
    "message": "This postcard needs 165 credits."
  }
}
  • GET/api/v1/fonts

    List the user's fonts (no stroke data).

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/fonts" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY"
    Response
    {
      "fonts": [
        {
          "id": "fnt_8k2m4q",
          "name": "My hand",
          "mode": "quick",
          "createdAt": "2026-09-26T10:00:00.000Z",
          "updatedAt": "2026-09-26T10:00:00.000Z",
          "glyphCount": 26,
          "alternateCount": 0
        }
      ]
    }
  • POST/api/v1/fonts

    Create a font from strokes. Points are em units 0–1000, y down.

    creditsEarned is paid once per font.

    Request
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/fonts" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"name":"My hand","mode":"quick","glyphs":{"a":[{"char":"a","source":"drawn","strokes":[[{"x":520,"y":300,"t":0},{"x":380,"y":260,"t":40},{"x":300,"y":420,"t":90},{"x":420,"y":520,"t":140},{"x":540,"y":380,"t":190},{"x":560,"y":540,"t":240}]]}]},"settings":{"weight":1,"smoothing":0.4,"sizeBalance":0.5,"slant":0},"consent":{"dataContribution":false}}'
    Response
    {
      "font": {
        "name": "My hand",
        "mode": "quick",
        "glyphs": {
          "a": [
            {
              "char": "a",
              "source": "drawn",
              "strokes": [
                [
                  {
                    "x": 520,
                    "y": 300,
                    "t": 0
                  },
                  {
                    "x": 380,
                    "y": 260,
                    "t": 40
                  },
                  {
                    "x": 300,
                    "y": 420,
                    "t": 90
                  },
                  {
                    "x": 420,
                    "y": 520,
                    "t": 140
                  },
                  {
                    "x": 540,
                    "y": 380,
                    "t": 190
                  },
                  {
                    "x": 560,
                    "y": 540,
                    "t": 240
                  }
                ]
              ]
            }
          ]
        },
        "settings": {
          "weight": 1,
          "smoothing": 0.4,
          "sizeBalance": 0.5,
          "slant": 0
        },
        "consent": {
          "dataContribution": false
        },
        "id": "fnt_8k2m4q",
        "createdAt": "2026-09-26T10:00:00.000Z",
        "updatedAt": "2026-09-26T10:00:00.000Z"
      },
      "creditsEarned": 10
    }
  • GET/api/v1/fonts/fnt_8k2m4q

    Get one font with every glyph and its alternates.

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/fonts/fnt_8k2m4q" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY"
    Response
    {
      "font": {
        "name": "My hand",
        "mode": "quick",
        "glyphs": {
          "a": [
            {
              "char": "a",
              "source": "drawn",
              "strokes": [
                [
                  {
                    "x": 520,
                    "y": 300,
                    "t": 0
                  },
                  {
                    "x": 380,
                    "y": 260,
                    "t": 40
                  },
                  {
                    "x": 300,
                    "y": 420,
                    "t": 90
                  },
                  {
                    "x": 420,
                    "y": 520,
                    "t": 140
                  },
                  {
                    "x": 540,
                    "y": 380,
                    "t": 190
                  },
                  {
                    "x": 560,
                    "y": 540,
                    "t": 240
                  }
                ]
              ]
            }
          ]
        },
        "settings": {
          "weight": 1,
          "smoothing": 0.4,
          "sizeBalance": 0.5,
          "slant": 0
        },
        "consent": {
          "dataContribution": false
        },
        "id": "fnt_8k2m4q",
        "createdAt": "2026-09-26T10:00:00.000Z",
        "updatedAt": "2026-09-26T10:00:00.000Z"
      }
    }
  • PUT/api/v1/fonts/fnt_8k2m4q

    Replace a font’s editable fields.

    Request
    curl -s -X PUT "$HANDWRITTEN_BASE_URL/api/v1/fonts/fnt_8k2m4q" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"name":"My hand, tidier","mode":"quick","glyphs":{"a":[{"char":"a","source":"drawn","strokes":[[{"x":520,"y":300,"t":0},{"x":380,"y":260,"t":40},{"x":300,"y":420,"t":90},{"x":420,"y":520,"t":140},{"x":540,"y":380,"t":190},{"x":560,"y":540,"t":240}]]}]},"settings":{"weight":1,"smoothing":0.7,"sizeBalance":0.5,"slant":0},"consent":{"dataContribution":false}}'
    Response
    {
      "font": {
        "name": "My hand, tidier",
        "mode": "quick",
        "glyphs": {
          "a": [
            {
              "char": "a",
              "source": "drawn",
              "strokes": [
                [
                  {
                    "x": 520,
                    "y": 300,
                    "t": 0
                  },
                  {
                    "x": 380,
                    "y": 260,
                    "t": 40
                  },
                  {
                    "x": 300,
                    "y": 420,
                    "t": 90
                  },
                  {
                    "x": 420,
                    "y": 520,
                    "t": 140
                  },
                  {
                    "x": 540,
                    "y": 380,
                    "t": 190
                  },
                  {
                    "x": 560,
                    "y": 540,
                    "t": 240
                  }
                ]
              ]
            }
          ]
        },
        "settings": {
          "weight": 1,
          "smoothing": 0.7,
          "sizeBalance": 0.5,
          "slant": 0
        },
        "consent": {
          "dataContribution": false
        },
        "id": "fnt_8k2m4q",
        "createdAt": "2026-09-26T10:00:00.000Z",
        "updatedAt": "2026-09-26T10:20:00.000Z"
      },
      "creditsEarned": 0
    }
  • DELETE/api/v1/fonts/fnt_8k2m4q

    Delete a font.

    Request
    curl -s -X DELETE "$HANDWRITTEN_BASE_URL/api/v1/fonts/fnt_8k2m4q" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY"
    Response
    {
      "deleted": true
    }
  • GET/api/v1/fonts/fnt_8k2m4q/ttf

    Download the font as TrueType.

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/fonts/fnt_8k2m4q/ttf" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY" \
      -o my-hand.ttf

    Response: binary font/ttf.

  • GET/api/v1/library

    List the free library fonts, each with its attribution.

    A library font id works as fontId for /preview and /sends too.

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/library"
    Response
    {
      "fonts": [
        {
          "id": "lib_fieldnote",
          "name": "Fieldnote",
          "description": "Upright print, 78 recorded characters from one writer.",
          "glyphCount": 78,
          "attribution": {
            "dataset": "UJI Pen Characters v2",
            "license": "CC-BY-4.0",
            "text": "UJI Pen Characters (Version 2), F. Prat, M. Castro, D. Llorens, A. Marzal, J. Vilar. https://doi.org/10.24432/C5FG8S. CC BY 4.0.",
            "url": "https://doi.org/10.24432/C5FG8S"
          }
        }
      ]
    }
  • GET/api/v1/library/lib_fieldnote

    A library font as an editable FontProject, to customise and save to /fonts as your own.

    Keep credits: it carries the CC BY 4.0 attribution into every font and letter made from it.

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/library/lib_fieldnote"
    Response
    {
      "font": {
        "id": "lib_fieldnote",
        "name": "Fieldnote",
        "createdAt": "2026-09-26T10:00:00.000Z",
        "updatedAt": "2026-09-26T10:00:00.000Z",
        "mode": "full",
        "glyphs": {
          "a": [
            {
              "char": "a",
              "source": "library",
              "strokes": [
                [
                  {
                    "x": 402,
                    "y": 610
                  },
                  {
                    "x": 330,
                    "y": 700
                  },
                  {
                    "x": 420,
                    "y": 760
                  }
                ]
              ]
            }
          ]
        },
        "settings": {
          "weight": 1,
          "smoothing": 0.42857142857142855,
          "sizeBalance": 0.5,
          "slant": 0
        },
        "consent": {
          "dataContribution": false
        },
        "credits": "UJI Pen Characters (Version 2), F. Prat, M. Castro, D. Llorens, A. Marzal, J. Vilar. https://doi.org/10.24432/C5FG8S. CC BY 4.0."
      },
      "attribution": {
        "dataset": "UJI Pen Characters v2",
        "license": "CC-BY-4.0",
        "text": "UJI Pen Characters (Version 2), F. Prat, M. Castro, D. Llorens, A. Marzal, J. Vilar. https://doi.org/10.24432/C5FG8S. CC BY 4.0.",
        "url": "https://doi.org/10.24432/C5FG8S"
      }
    }
  • GET/api/v1/library/lib_fieldnote/ttf

    Download a library font.

    Works signed out. Signed in, the X-Credits-Earned response header says what the download earned (once per library font, a few a day).

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/library/lib_fieldnote/ttf" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY" \
      -o fieldnote.ttf -D -

    Response: binary font/ttf.

  • POST/api/v1/preview

    Render a letter or postcard without sending. format: pdf (default), png (first page / postcard back, 144 dpi) or svg.

    Library fonts preview without a key. For a postcard, recipient is sketched into the address strip.

    Request
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/preview" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"kind":"postcard","fontId":"fnt_8k2m4q","text":"Happy birthday, Grandma! The garden says hi.","format":"pdf","seed":42,"recipient":{"name":"Rosa Alvarez","line1":"418 Willow Street","city":"Burlington","region":"VT","postalCode":"05401","country":"US"}}' \
      -o preview.pdf

    Response: binary application/pdf.

  • GET/api/v1/capabilities

    What this deployment does right now: whether sends are mailed, and whether card payment is on.

    fulfilment "dry-run" is test mode: sends are rendered to a PDF proof and nothing is mailed. With cardPayments false, POST /sends refuses "paymentMethod": "stripe" (503) before creating anything.

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/capabilities"
    Response
    {
      "cardPayments": false,
      "fulfilment": "dry-run"
    }
  • POST/api/v1/sends

    Mail a letter or postcard. Pays with credits, or returns a Stripe checkoutUrl.

    With "paymentMethod": "stripe" the status is awaiting_payment and checkoutUrl is set; nothing is mailed until the user pays. Each agent API key can make 5 sends a UTC day (429 rate_limited after). US recipients only for now.

    Request
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/sends" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"kind":"postcard","fontId":"fnt_8k2m4q","text":"Happy birthday, Grandma! The garden says hi.","recipient":{"name":"Rosa Alvarez","line1":"418 Willow Street","city":"Burlington","region":"VT","postalCode":"05401","country":"US"},"paymentMethod":"credits","seed":42}'
    Response
    {
      "id": "snd_3j9x1c",
      "status": "queued",
      "priceCents": 165,
      "creditsUsed": 165
    }
  • GET/api/v1/sends?limit=20

    Your sends, newest first.

    Pass nextCursor back as cursor for the next page; it is absent on the last page. limit is 1–100.

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/sends?limit=20" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY"
    Response
    {
      "sends": [
        {
          "id": "snd_3j9x1c",
          "status": "submitted",
          "priceCents": 165,
          "creditsUsed": 165,
          "kind": "postcard",
          "fontId": "fnt_8k2m4q",
          "recipient": {
            "name": "Rosa Alvarez",
            "line1": "418 Willow Street",
            "city": "Burlington",
            "region": "VT",
            "postalCode": "05401",
            "country": "US"
          },
          "createdAt": "2026-09-26T10:00:00.000Z",
          "updatedAt": "2026-09-26T10:03:00.000Z",
          "fulfilmentRef": "mc_0f41b2",
          "pdfUrl": "/api/v1/sends/snd_3j9x1c/pdf"
        }
      ],
      "nextCursor": "41"
    }
  • GET/api/v1/sends/snd_3j9x1c

    Status of a send, with the proof PDF once rendered.

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/sends/snd_3j9x1c" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY"
    Response
    {
      "id": "snd_3j9x1c",
      "status": "submitted",
      "priceCents": 165,
      "creditsUsed": 165,
      "kind": "postcard",
      "fontId": "fnt_8k2m4q",
      "recipient": {
        "name": "Rosa Alvarez",
        "line1": "418 Willow Street",
        "city": "Burlington",
        "region": "VT",
        "postalCode": "05401",
        "country": "US"
      },
      "createdAt": "2026-09-26T10:00:00.000Z",
      "updatedAt": "2026-09-26T10:03:00.000Z",
      "fulfilmentRef": "mc_0f41b2",
      "pdfUrl": "/api/v1/sends/snd_3j9x1c/pdf"
    }
  • GET/api/v1/sends/snd_3j9x1c/pdf

    The rendered PDF of your own send (404 until rendered).

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/sends/snd_3j9x1c/pdf" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY" \
      -o letter.pdf

    Response: binary application/pdf.

  • GET/api/v1/credits

    Balance and ledger, most recent first.

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/credits" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY"
    Response
    {
      "balance": 12,
      "entries": [
        {
          "id": 2,
          "delta": 2,
          "reason": "library_download",
          "ref": "lib_fieldnote",
          "at": "2026-09-26T09:40:00.000Z"
        },
        {
          "id": 1,
          "delta": 10,
          "reason": "font_created",
          "ref": "fnt_8k2m4q",
          "at": "2026-09-26T09:30:00.000Z"
        }
      ]
    }
  • GET/api/v1/keys

    List your active API keys and their scopes.

    Request
    # Browser session only (passkey sign-in); API keys get 403. \
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/keys"
    Response
    {
      "keys": [
        {
          "id": "key_71hd",
          "name": "Claude Desktop",
          "prefix": "hw_ab12cd34",
          "scopes": [
            "fonts:read",
            "fonts:write",
            "sends:write"
          ],
          "createdAt": "2026-09-26T10:00:00.000Z",
          "lastUsedAt": "2026-09-26T11:00:00.000Z"
        }
      ]
    }
  • POST/api/v1/keys

    Mint an API key (do this on the account page).

    The secret is shown exactly once; only its hash is stored. Scopes: fonts:read fonts:write sends:write.

    Request
    # Browser session only (passkey sign-in); API keys get 403. \
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/keys" \
      -H "Content-Type: application/json" \
      -d '{"name":"Claude Desktop"}'
    Response
    {
      "key": {
        "id": "key_71hd",
        "name": "Claude Desktop",
        "prefix": "hw_ab12cd34",
        "scopes": [
          "fonts:read",
          "fonts:write",
          "sends:write"
        ],
        "createdAt": "2026-09-26T10:00:00.000Z"
      },
      "secret": "hw_ab12cd34_…(shown once)"
    }
  • DELETE/api/v1/keys/key_71hd

    Revoke an API key.

    Request
    # Browser session only (passkey sign-in); API keys get 403. \
    curl -s -X DELETE "$HANDWRITTEN_BASE_URL/api/v1/keys/key_71hd"
    Response
    {
      "deleted": true
    }
  • POST/api/v1/consent

    Opt a font in or out of handwriting-data contribution.

    The person’s own decision: a browser session, or a phone key linked through /connect/android (scope consent:write). Agent keys get 403. false revokes and deletes the samples that font contributed.

    Request
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/consent" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"fontId":"fnt_8k2m4q","dataContribution":false}'
    Response
    {
      "fontId": "fnt_8k2m4q",
      "consent": {
        "dataContribution": false,
        "at": "2026-09-26T10:00:00.000Z"
      },
      "samplesDeleted": 26
    }
  • GET/api/v1/auth/me

    Who this session or key belongs to (401 when signed out).

    Request
    curl -s "$HANDWRITTEN_BASE_URL/api/v1/auth/me" \
      -H "Authorization: Bearer $HANDWRITTEN_API_KEY"
    Response
    {
      "user": {
        "id": "usr_5f2a",
        "displayName": "Ada",
        "createdAt": "2026-09-26T10:00:00.000Z"
      },
      "via": "key"
    }
  • POST/api/v1/auth/register/options

    Start creating an account with a passkey: WebAuthn creation options { options }.

    Then POST /auth/register/verify { response } with the browser credential; answers { user: { id } } and sets the session cookie.

    Request
    # Browser session only (passkey sign-in); API keys get 403. \
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/auth/register/options" \
      -H "Content-Type: application/json" \
      -d '{"name":"Ada"}'
    Response
    {
      "options": "…PublicKeyCredentialCreationOptionsJSON…"
    }
  • POST/api/v1/auth/login/options

    Start a passkey sign-in: WebAuthn request options { options }.

    Then POST /auth/login/verify { response }; answers { user: { id } } and sets the session cookie. POST /auth/logout ends the session.

    Request
    # Browser session only (passkey sign-in); API keys get 403. \
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/auth/login/options"
    Response
    {
      "options": "…PublicKeyCredentialRequestOptionsJSON…"
    }
  • POST/api/v1/auth/login/verify

    Finish a passkey sign-in with the browser’s credential JSON.

    Request
    # Browser session only (passkey sign-in); API keys get 403. \
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/auth/login/verify" \
      -H "Content-Type: application/json" \
      -d '{"response":"…AuthenticationResponseJSON…"}'
    Response
    {
      "user": {
        "id": "usr_5f2a"
      }
    }
  • POST/api/v1/connect/android/approve

    The /connect/android page approves linking the Android app; answers the mailcraftfonts:// redirect.

    The code in the redirect is single-use, lasts 5 minutes, is stored hashed and is bound to the PKCE challenge (S256).

    Request
    # Browser session only (passkey sign-in); API keys get 403. \
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/connect/android/approve" \
      -H "Content-Type: application/json" \
      -d '{"state":"Zq4…state","codeChallenge":"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM","app":"io.kept.handwriting.fonts"}'
    Response
    {
      "redirectUrl": "mailcraftfonts://connect?state=Zq4%E2%80%A6state&code=…",
      "expiresAt": "2026-09-26T10:05:00.000Z"
    }
  • POST/api/v1/connect/android/exchange

    The app trades its one-time code and PKCE verifier for its own API key.

    No auth header: the code and verifier are the proof. A wrong verifier burns the code. The key can also change data consent (consent:write).

    Request
    curl -s -X POST "$HANDWRITTEN_BASE_URL/api/v1/connect/android/exchange" \
      -H "Content-Type: application/json" \
      -d '{"code":"one-time-code-from-the-redirect","codeVerifier":"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"}'
    Response
    {
      "key": {
        "id": "key_a9c2",
        "name": "Android phone (io.kept.handwriting.fonts)",
        "prefix": "hw_9f00e1c2",
        "scopes": [
          "fonts:read",
          "fonts:write",
          "sends:write",
          "consent:write"
        ],
        "createdAt": "2026-09-26T10:00:00.000Z"
      },
      "secret": "hw_9f00e1c2_…(shown once)"
    }

Send statuses

awaiting_payment
Waiting for the user to pay at checkoutUrl.
queued
Paid; waiting to be rendered.
rendering
Being written out in the font.
rendered_dry_run
Test mode: rendered and recorded, not mailed.
submitted
Handed to the print-and-mail partner.
mailed
In the post.
failed
Could not be completed; the error field says why.
cancelled
Stopped before mailing.
needs_review
The mail service did not confirm it; a person checks, then refunds or submits it. Do not resend.