Complete letter job transcript
A whole letter job from the price question to the way back out, without the human ever leaving the conversation. This transcript deliberately runs on a LIVE token with the full_mcp bundle: a test token always takes the auto-send tail and therefore cannot show the approval queue, which is the single most important step of a real send. The only preconditions are a complete sender profile and funds in the wallet.
- Setup: a tenant advice service runs a LIVE token with the full_mcp bundle. Unlike the other two transcripts this one is not a sandbox run, because a test token always takes the auto-send tail and therefore cannot show the approval queue. Everything below is a real send: real cents, a real letter, a real human decision.
- Tool call
mcp_health
- Result
{ "status": "ok", "code": "OK", "message": "Verbindung geprüft.", "data": { "healthy": true, "isTest": false }, "requestId": "req_4b7e91a0" } - Step 1: is there money for this job at all? wallet_balance answers before anything is composed. If the balance were short, the next call would be wallet_topup_link, whose link the human opens once.
- Tool call
wallet_balance {} - Result
{ "status": "ok", "code": "OK", "message": "Wallet geladen.", "data": { "balanceEuros": "42.10", "dailyCapLetters": 50, "dailyUsedLetters": 3, "dailyCapEuros": "250.00", "dailyUsedEuros": "10.47", "nextResetAt": "2026-07-26T00:00:00Z" }, "requestId": "req_c10a4f82" } - Step 2: what would this cost? At this point there is no letter yet, only an intent: two pages, black and white, registered mail (Einwurf) inside Germany. That is exactly the question shipping_quote answers.
- Tool call
shipping_quote { "pageCount": 2, "color": false, "deliveryType": "einschreiben_einwurf", "country": "DE" } - Result
{ "status": "ok", "code": "OK", "message": "Preis ermittelt.", "data": { "userCents": 719, "userEuros": "7.19", "letterFormat": "standard", "estimatedDeliveryRange": "ungefähr 1 bis 3 Werktage", "fulfillmentProvider": "letterxpress", "legalWeight": "einschreiben_einwurf", "source": "catalog" }, "requestId": "req_2d81b0e5" } - Step 3: the letter itself. letter_create_draft composes the body into a DIN 5008 PDF and returns a letterId plus a preview link. Nothing is charged and nothing is sent. totalCents here prices the default standard letter, not the registered mail chosen above; the binding figure arrives with the send.
- Tool call
letter_create_draft { "subject": "Widerspruch gegen die Betriebskostenabrechnung 2025", "content": "Sehr geehrte Damen und Herren,\n\nhiermit widerspreche ich der Betriebskostenabrechnung für das Jahr 2025 vom 02.06.2026. Die abgerechneten Kosten für die Gartenpflege sind gegenüber dem Vorjahr um 240 Prozent gestiegen, ohne dass ein Grund erkennbar wäre. Die Belege sind der Anlage zu entnehmen.\n\nMit freundlichen Grüßen\nAnna Berger", "recipientName": "Hausverwaltung Lindner GmbH", "recipientStreet": "Gartenstraße", "recipientHouseNumber": "18", "recipientZip": "04103", "recipientCity": "Leipzig", "recipientCountry": "DE" } - Result
{ "status": "ok", "code": "OK", "message": "Entwurf angelegt.", "data": { "letterId": "7d2a1c50-4b8e-4c31-9f0a-3e6b5d1c8a90", "wordCount": 84, "pageCount": 1, "previewUrl": "https://storage.frankki.app/previews/7d2a1c50-4b8e-4c31-9f0a-3e6b5d1c8a90.pdf?X-Amz-Expires=86400", "deliveryType": "standard", "deliveryTypeLabel": "Standardbrief", "totalCents": 349, "totalEuros": 3.49, "totalFormatted": "3,49 €" }, "requestId": "req_6f30ca19" } - Step 4: the evidence. attachment_upload_pdf appends the receipts to the same letterId. The merged page count is checked before the file is stored, so an oversized attachment fails here and not halfway through a send.
- Tool call
attachment_upload_pdf { "letterId": "7d2a1c50-4b8e-4c31-9f0a-3e6b5d1c8a90", "title": "Belege Gartenpflege 2025", "pdfUrl": "https://files.example.de/belege-gartenpflege-2025.pdf" } - Result
{ "status": "ok", "code": "OK", "message": "Anhang gespeichert.", "data": { "attachmentId": "c48f0a97-2d15-4e6b-9a83-70cb1e5d4f28", "pageCount": 1, "fileSize": 184320 }, "requestId": "req_af52d704" } - Step 5: the send. Note what comes back: orderId is null and status is "pending_approval". A live MCP send does not dispatch, it queues. The human is notified and the returned previewUrl plus costEuros are exactly what they are shown before deciding.
- Tool call
order_send { "clientOrderId": "widerspruch-bk-2025-berger", "letterId": "7d2a1c50-4b8e-4c31-9f0a-3e6b5d1c8a90", "deliveryType": "einschreiben_einwurf" } - Result
{ "status": "ok", "code": "OK", "message": "Freigabe angefordert.", "data": { "approvalId": "2f6c8b13-05a4-4d9e-8c17-b40e9a3f5d62", "orderId": null, "letterId": "7d2a1c50-4b8e-4c31-9f0a-3e6b5d1c8a90", "costCents": 719, "costEuros": 7.19, "deliveryType": "einschreiben_einwurf", "deliveryTypeLabel": "Einschreiben Einwurf", "previewUrl": "https://storage.frankki.app/previews/7d2a1c50-4b8e-4c31-9f0a-3e6b5d1c8a90-final.pdf?X-Amz-Expires=86400", "mode": "review", "expiresAt": "2026-07-25T09:22:41Z", "status": "pending_approval", "isReplay": false, "fulfillmentProvider": "letterxpress", "einlieferungsbelegPending": true }, "requestId": "req_88e1c063" } - Step 6: the decision. Before calling approve the agent shows the human the preview PDF, the exact 7,19 euros and the delivery type, and only then sets user_saw_preview:true. A chat approval does not dispatch immediately: dispatchAt is ten minutes out and cancellable stays true until then, so "stop" still works.
- Tool call
approval_decide { "approvalId": "2f6c8b13-05a4-4d9e-8c17-b40e9a3f5d62", "decision": "approve", "user_saw_preview": true, "comment": "Anna hat Vorschau, Preis 7,19 Euro und Einschreiben Einwurf bestätigt." } - Result
{ "status": "ok", "code": "OK", "message": "Freigabe erteilt.", "data": { "approvalId": "2f6c8b13-05a4-4d9e-8c17-b40e9a3f5d62", "orderId": "e91b4d07-6c22-4a8f-b3d5-19f7c0a2e441", "status": "approved", "decidedBy": "mcp:token_4f1a", "decidedAt": "2026-07-25T09:14:02Z", "approvedVia": "chat", "dispatchAt": "2026-07-25T09:24:02Z", "cancellable": true, "itemCount": 1, "totalCents": 719, "totalEuros": 7.19, "totalFormatted": "7,19 €", "deliveryType": "einschreiben_einwurf", "deliveryTypeLabel": "Einschreiben Einwurf", "isReplay": false }, "requestId": "req_31bd7e40" } - Step 7: tracking. The approval produced the orderId; order_status follows it from here. Poll it again later for the delivery events, and for registered mail the tracking code appears as soon as the provider assigns it.
- Tool call
order_status { "orderId": "e91b4d07-6c22-4a8f-b3d5-19f7c0a2e441" } - Result
{ "status": "ok", "code": "OK", "message": "Status geladen.", "data": { "status": "processing", "timeline": [ { "source": "frankki", "title": "Auftrag angelegt", "occurredAt": "2026-07-25T09:24:02Z" }, { "source": "letterxpress", "title": "An den Versandpartner übergeben", "occurredAt": "2026-07-25T09:26:11Z" } ], "trackingCode": null, "cancellable": true }, "requestId": "req_7ac09e12" } - Recovery branch A, cancel. A second letter from the same day is stopped before it is printed. Money goes back onto the wallet as a credit, never onto a card: FrankKi runs a closed loop. Once the letter is printing, order_cancel refuses cleanly instead of pretending.
- Tool call
order_cancel { "orderId": "8a3e7f26-91d0-4b5c-a2e8-6d4f0b7c9153" } - Result
{ "status": "ok", "code": "OK", "message": "Auftrag storniert.", "data": { "orderId": "8a3e7f26-91d0-4b5c-a2e8-6d4f0b7c9153", "status": "cancelled", "cancelled": true, "idempotent": false, "refundedEuros": 0, "walletCreditedEuros": 3.49, "costCents": 349 }, "requestId": "req_5e4b02af" } - Recovery branch B, fix and resubmit. A third letter came back from the provider with a rejected address. order_status names it: awaiting_partner_fix.
- Tool call
order_status { "orderId": "46b0d9e5-73a2-4f18-8c6d-05e2a7b1f394" } - Result
{ "status": "ok", "code": "OK", "message": "Status geladen.", "data": { "status": "awaiting_partner_fix", "timeline": [ { "source": "frankki", "title": "Auftrag angelegt", "occurredAt": "2026-07-25T09:40:18Z" }, { "source": "letterxpress", "title": "Adresse vom Versandpartner abgelehnt: Hausnummer fehlt", "occurredAt": "2026-07-25T09:41:55Z" } ], "cancellable": false }, "requestId": "req_0d97fc35" } - The agent supplies the corrected recipient and the order is re-composed and re-enqueued. Nothing is charged a second time: the money already booked for this order stays where it is.
- Tool call
order_fix_resubmit { "orderId": "46b0d9e5-73a2-4f18-8c6d-05e2a7b1f394", "recipient": { "name": "Stadtwerke Leipzig GmbH", "street": "Arno-Nitzsche-Straße", "houseNumber": "35", "zip": "04277", "city": "Leipzig", "country": "DE" } } - Result
{ "status": "ok", "code": "OK", "message": "Auftrag erneut eingereicht.", "data": { "orderId": "46b0d9e5-73a2-4f18-8c6d-05e2a7b1f394", "status": "processing", "resubmittedAt": "2026-07-25T09:47:30Z", "fulfillmentProvider": "letterxpress", "isReplay": false }, "requestId": "req_b6210d8e" } - That is the whole job inside one conversation: check funds, quote, draft, attach, send, get human approval, track, and both ways back out. No step ejects the human into the dashboard, except the one step that should: the approval itself, which they can also decide there.
Tools used
Every tool here is fully documented in the tool reference (fields, scope, dryRun).
Trust and live status
Machine-readable service facts and the current operational status of the partner interface are publicly available at all times, no login needed.