/api/qmail/net/messages/upload_and_tell

GET POST

Convenience compose endpoint. Runs qmail_upload_files() and qmail_send_tells() back-to-back inside a single background task; one task_id covers both phases. Replaces the legacy /api/qmail/net/messages/send (deleted in Phase 3.7).

Description

The /api/qmail/net/messages/upload_and_tell endpoint composes the two-step send into a single async call. It accepts the same inputs as /upload plus a required recipient list (to, cc, or bcc). The background worker first stages the CBDF on the QMail RAIDA, then fires Tell2 to each recipient's beacon RAIDA. A single task_id tracks both phases.

When to Use This vs. /upload + /tell

Use /upload_and_tell when you have a finalized message ready to deliver immediately. Use the two-step flow (/upload then /tell) when you need to:

  • Stage a draft, inspect the receipt, and only fire Tell after explicit confirmation.
  • Retry a Tell after fixing some upstream issue without re-staging the upload.
  • Fire multiple delayed Tells from the same upload — though note Q25(b): re-Tell on a started receipt returns 409, so this only works once per upload.
Replacing the Legacy /send

The legacy /api/qmail/net/messages/send endpoint was deleted in commit fd56a24 (Phase 3.7). Existing code that called /send should switch to /upload_and_tell — same input shape, no synchronous result. The most visible behavioral difference is that the response is now async-only: the caller gets a task_id immediately and must poll /api/system/tasks for completion.

Pool Semantics (Q18(a) Split Commit)

Internally this endpoint uses the same split-commit primitives as the two-step flow:

  • Storage lockers are committed at upload time.
  • Inbox-fee locker is committed only after Tell succeeds; on failure it stays RESERVED for the (deferred) Phase 8 reclamation sweep.

Net effect: a failed Tell phase does not double-spend the inbox fee — no Tell, no fee sunk.

Parameters

Send parameters as form data (POST) or query string values (GET). All inputs from /upload are accepted; recipients are required.

Parameter Type Required Description
body string One of three Inline plain-text email body. Mutually exclusive with plain_text_qmail_path and email_file.
plain_text_qmail_path string (path) One of three Server-side path to a plain-text body file (max 1 MB).
email_file string (path) One of three Server-side path to a pre-built .qmail CBDF file. Bypasses CBDF encoding entirely.
attachment_file_path string (path), repeatable No Server-side path to one attachment. Repeat the parameter for multiple attachments. Legacy form: attachments=A,B,C.
to string, repeatable Yes (one of to/cc/bcc) Recipient address. Repeat the parameter or pass a comma/semicolon-separated string. At least one recipient (to, cc, or bcc) is required.
cc string, repeatable Yes (one of to/cc/bcc) CC recipient. Same shape as to.
bcc string, repeatable Yes (one of to/cc/bcc) BCC recipient. Same shape as to. NOT stamped into the CBDF visible-metadata block.
subject string No Email subject line. Stamped into the CBDF and stored on the receipt.
file_guid string (32 hex chars) No Optional caller-supplied GUID. Returns HTTP 409 if a receipt already exists for that GUID.
duration integer (weeks) No Storage duration. Default 4; clamped to [1, 520].
wallet_path / wallet string No Wallet that funds storage and inbox-fee lockers. Defaults to Default.
debug boolean No Set debug=true to enable per-request LOG_DEBUG verbosity for this one call.
Recipients Are Required

If none of to, cc, or bcc is supplied, the call fails with HTTP 400 and the message "At least one recipient required (to/cc/bcc)". For an upload with no Tell, use /upload instead.

Response

The handler returns HTTP 200 OK immediately after spawning the worker. Poll /api/system/tasks?id=<task_id> for status; both phases progress under the same task_id. The receipt at /api/qmail/receipts?guid=<file_guid> reflects upload state (immediately) and Tell state (after Tell phase completes).

Success Response Properties (immediate)

success boolean
true when the worker thread was spawned successfully.
command string
Always qmail-upload-and-tell.
task_id string
Identifier for the background upload+tell task.
url string
Full polling URL for the task.
file_guid string
The 32-character hex GUID assigned to this upload. Use it with /receipts.
duration integer
Storage duration (weeks).
message string
Human-readable hint that both phases are running.

Success Response Example (immediate)

{
  "success": true,
  "command": "qmail-upload-and-tell",
  "task_id": "task_3f2a1c40",
  "url": "/api/system/tasks?id=task_3f2a1c40",
  "file_guid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "duration": 4,
  "message": "Upload+Tell started — poll /api/system/tasks for status, or GET /api/qmail/receipts?guid=... for receipt detail"
}

Task Poll Example (both phases complete — all accepted)

{
  "status": "success",
  "task_id": "task_3f2a1c40",
  "progress": 100,
  "message": "Upload + Tell complete: all beacons accepted",
  "data": {
    "file_guid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "upload_successes": 25,
    "upload_failures": 0,
    "beacons": 1,
    "tell_successes": 1,
    "tell_failures": 0,
    "all_accepted": true
  }
}

Task Poll Example (Tell phase failed mid-flight)

{
  "status": "failed",
  "task_id": "task_3f2a1c40",
  "progress": 75,
  "message": "Tell phase failed: RESULT_NETWORK_ERROR — Beacon RAIDA unreachable"
}

Error Response — Missing Recipients (HTTP 400)

{
  "error": true,
  "message": "At least one recipient required (to/cc/bcc)",
  "code": 400
}

Interactive API Tester

Test this Endpoint

http://localhost:8081/api/qmail/net/messages/upload_and_tell?body=QMail%20upload%2Btell%20doc%20test%20from%20Client1.&subject=Upload%2BTell%20Doc%20Test&to=%40chariot.harbor.byte&duration=4

Examples

cURL — Body Mode

curl "http://localhost:8081/api/qmail/net/messages/upload_and_tell?body=QMail%20upload%2Btell%20doc%20test%20from%20Client1.&subject=Upload%2BTell%20Doc%20Test&to=%40chariot.harbor.byte&duration=4"

cURL — File Mode with Attachments

curl "http://localhost:8081/api/qmail/net/messages/upload_and_tell?email_file=E%3A%5CClient1%5CClient_Data%5CWallets%5CMail%5COutgoing%5Ctest1.qmail&attachment_file_path=E%3A%5CClient1%5CClient_Data%5CWallets%5CMail%5COutgoing%5Cattachment1.pdf&attachment_file_path=E%3A%5CClient1%5CClient_Data%5CWallets%5CMail%5COutgoing%5Cattachment2.pdf&subject=Upload%2BTell%20File%20Doc%20Test&to=%40chariot.harbor.byte&duration=4"

JavaScript (fetch)

const compose = await fetch('http://localhost:8081/api/qmail/net/messages/upload_and_tell?body=QMail%20upload%2Btell%20doc%20test%20from%20Client1.&subject=Upload%2BTell%20Doc%20Test&to=%40chariot.harbor.byte&duration=4').then(r => r.json());
if (!compose.success) {
    throw new Error(`Upload+Tell kickoff failed: ${compose.message}`);
}
console.log(`task=${compose.task_id} guid=${compose.file_guid}`);

// Poll until both phases finish.
async function pollTask(taskId) {
    while (true) {
        const r = await fetch(`/api/system/tasks?id=${encodeURIComponent(taskId)}`);
        const t = await r.json();
        if (t.status !== 'running') return t;
        await new Promise(r => setTimeout(r, 1000));
    }
}
const finalState = await pollTask(compose.task_id);
console.log('Final state:', finalState);

// Inspect the receipt for per-recipient detail.
const receipt = await fetch(`/api/qmail/receipts?guid=${compose.file_guid}`).then(r => r.json());
console.log('Recipients:', receipt.tell?.recipients);

Python

import time
import requests

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

compose = requests.get(f'{BASE}/qmail/net/messages/upload_and_tell', params={
    'body':    'QMail upload+tell doc test from Client1.',
    'subject': 'Upload+Tell Doc Test',
    'to':      '@chariot.harbor.byte',
    'duration': 4,
}).json()
assert compose['success'], compose
task_id   = compose['task_id']
file_guid = compose['file_guid']

while True:
    t = requests.get(f'{BASE}/system/tasks', params={'id': task_id}).json()
    if t['status'] != 'running':
        break
    time.sleep(1)
print('Final state:', t)

receipt = requests.get(f'{BASE}/qmail/receipts', params={'guid': file_guid}).json()
print('Recipients:', receipt.get('tell', {}).get('recipients'))

Important Notes

Single Task ID for Both Phases

The same task_id is updated through both upload and Tell phases. Watch the progress field move from upload to Tell, and the message field for phase markers.

Failed Tell — Receipt Still Useful

If the upload phase succeeds but Tell fails, the receipt is still written to <Mail>/Receipts/<file_guid>.json. The retry worker (qmail_tell_retry.c) will keep attempting failed beacons in the background; check /api/qmail/db/tells/list_pending for queue inspection.

Limits
  • Plain-text body file: 1 MB max.
  • Up to 50 recipients per to/cc/bcc field.
  • Storage duration: 1–520 weeks.