/api/qmail/receipts

GET

Read-only receipt fetcher. Returns the JSON contents of <Mail-wallet>/Receipts/<file_guid>.json as application/json. The receipt is the canonical record of an email's upload + Tell phases — created by /upload and updated through the lifecycle by /tell and the retry worker.

Replace the Placeholder GUID

The link above uses 00000000000000000000000000000000 as a placeholder. Replace it with a real file_guid from an earlier /upload or /upload_and_tell response, otherwise the call returns HTTP 404.

Description

The /api/qmail/receipts endpoint streams the raw receipt JSON document for a single email. It is a thin read-only view — the server reads <Mail-wallet>/Receipts/<file_guid>.json and returns the bytes verbatim with Content-Type: application/json. There is no transformation, summarization, or filtering.

What You Can Learn From a Receipt
  • Upload phase: per-file stripe table (server_id, locker_code, is_parity), per-server-success counts, total group size.
  • Tell phase: per-recipient status (queued, sending, sent, failed, retry_queued), attempt counts, last_error, started/finished timestamps.
  • Identity: sender SN/denom/email (the NON-secret subset). Bearer tokens (sender_ans, enc_key.ans) are never serialized.
  • Pool reservations: inbox-fee locker key (string form), inbox_fee_acquired flag.
Receipts Are Mutated In Place

The retry worker (qmail_tell_retry.c) updates the receipt's matching recipient row on each retry via qmail_receipt_update_recipient(). Saves are atomic (tmp+rename), so a concurrent fetch always returns a consistent snapshot.

Receipts Never Contain Secrets

Per the security note in qmail_receipt.h, receipts hold only IDs, status, locker code strings, and metadata. sender_ans (the per-RAIDA AN bytes for the sender identity coin) and enc_key.ans (encryption-coin per-RAIDA AN bytes) are never serialized to disk — both are bearer tokens. /tell rehydrates them from the wallet at run time.

Parameters

Parameter Type Required Description
guid string (32 hex chars) Yes The file_guid returned by a prior /upload or /upload_and_tell call. Returns HTTP 404 if no receipt exists.

Response

On success, returns HTTP 200 OK with Content-Type: application/json and the raw receipt JSON bytes. The schema is documented in include/qmail/qmail_receipt.h (schema version 1). Below is an abbreviated example showing the most useful fields.

Success Response Example (Upload + Tell complete)

{
  "version": 1,
  "file_guid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "request_id": "5a91c802",
  "created_at": 1735689800,
  "updated_at": 1735689812,
  "wallet_path": "Default",
  "sender": {
    "serial_number": 55076,
    "denomination": 1,
    "email": "Sean.Worthington@Founder#FUV.giga"
  },
  "subject": "Upload+Tell Doc Test",
  "body_preview": "QMail upload+tell doc test from Client1.",
  "upload": {
    "status": "success",
    "started_at": 1735689800,
    "finished_at": 1735689805,
    "servers_total": 25,
    "servers_ok": 25,
    "servers_fail": 0,
    "total_group_size": 1024,
    "inbox_fee_locker": "FEE-001AABBCCDDEE",
    "inbox_fee_acquired": true,
    "files": [
      {
        "role": "email",
        "file_type": 1,
        "source": "body",
        "size_bytes": 1024,
        "status": "success",
        "stripe_count": 25,
        "stripes_uploaded": 25,
        "stripes_failed": 0,
        "stripes": [
          { "stripe_index": 0, "server_id": 0,  "is_parity": false, "ok": true,
            "stripe_size": 50, "locker_code": "STR-00-AABBCC11" },
          { "stripe_index": 1, "server_id": 1,  "is_parity": false, "ok": true,
            "stripe_size": 50, "locker_code": "STR-01-AABBCC22" }
        ]
      }
    ]
  },
  "tell": {
    "status": "success",
    "summary_total": 1,
    "summary_queued": 0,
    "summary_sent": 1,
    "summary_failed": 0,
    "recipients": [
      {
        "address": "Mohsin.Mehraj@FullStack#FVH.giga",
        "kind": "to",
        "serial_number": 55077,
        "denomination": 1,
        "beacon_raida": 11,
        "tell_command_code": 71,
        "status": "sent",
        "attempts": 1,
        "last_error": "",
        "started_at": 1735689810,
        "finished_at": 1735689812
      }
    ]
  }
}

Success Response Example (Upload-only — no Tell yet)

{
  "version": 1,
  "file_guid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "request_id": "5a91c802",
  "created_at": 1735689800,
  "updated_at": 1735689805,
  "upload": {
    "status": "success",
    "servers_ok": 25,
    "servers_fail": 0,
    "inbox_fee_acquired": true
  },
  "tell": {
    "status": "not_started",
    "summary_total": 1,
    "summary_queued": 1,
    "summary_sent": 0,
    "summary_failed": 0,
    "recipients": [
      {
        "address": "Mohsin.Mehraj@FullStack#FVH.giga",
        "kind": "to",
        "serial_number": 55077,
        "denomination": 1,
        "beacon_raida": 11,
        "tell_command_code": 71,
        "status": "queued",
        "attempts": 0
      }
    ]
  }
}

Success Response Example (Tell partial — retry queued)

{
  "tell": {
    "status": "partial",
    "summary_total": 2,
    "summary_sent": 1,
    "summary_failed": 0,
    "summary_queued": 1,
    "recipients": [
      { "address": "...giga", "kind": "to",  "status": "sent",
        "attempts": 1 },
      { "address": "...giga", "kind": "cc",  "status": "retry_queued",
        "attempts": 2, "last_error": "Beacon RAIDA timeout" }
    ]
  }
}

Error Response — Receipt Not Found (HTTP 404)

{
  "error": true,
  "message": "Receipt not found",
  "code": 404
}

Error Response — Missing/Invalid GUID (HTTP 400)

{
  "error": true,
  "message": "Invalid guid format (expected 32 hex chars)",
  "code": 400
}

Error Response — No Mail Wallet (HTTP 500)

{
  "error": true,
  "message": "No Mail wallet configured",
  "code": 500
}

Interactive API Tester

Test this Endpoint

Examples

cURL

curl "http://localhost:8081/api/qmail/receipts?guid=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"

JavaScript (fetch)

const fileGuid = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6';
const url = `http://localhost:8081/api/qmail/receipts?guid=${fileGuid}`;
const r = await fetch(url);
if (r.status === 404) {
    console.warn('No receipt for that GUID yet — upload may still be running.');
} else if (r.ok) {
    const receipt = await r.json();
    console.log(`Upload status: ${receipt.upload?.status}`);
    console.log(`Tell status:   ${receipt.tell?.status}`);
    receipt.tell?.recipients?.forEach(rc =>
        console.log(`  ${rc.kind} ${rc.address}: ${rc.status} (attempts=${rc.attempts})`)
    );
}

Python

import requests

BASE = 'http://localhost:8081/api'
file_guid = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6'

r = requests.get(f'{BASE}/qmail/receipts', params={'guid': file_guid})
if r.status_code == 404:
    print('No receipt for that GUID yet — upload may still be running.')
elif r.ok:
    receipt = r.json()
    print(f"Upload status: {receipt.get('upload', {}).get('status')}")
    print(f"Tell status:   {receipt.get('tell',   {}).get('status')}")
    for rc in receipt.get('tell', {}).get('recipients', []):
        print(f"  {rc['kind']} {rc['address']}: {rc['status']} (attempts={rc['attempts']})")

Important Notes

Schema Version

The receipt JSON includes "version": 1. Future schema changes will bump this and add a backwards-compatibility note in qmail_receipt.h.

Polling Cadence

This is a cheap read (one filesystem stat + one read of a few KB). Polling every 1–2 seconds during an active /upload_and_tell task is fine. For long-running uploads, prefer polling /api/system/tasks for the high-level status and only fetching the receipt when you need per-recipient detail.