/api/qmail/net/beacon/ping

GET

Long-poll beacon endpoint. Blocks until new mail arrives or the specified timeout expires, providing an efficient way to receive real-time mail notifications without constant polling.

Description

The /api/qmail/net/beacon/ping endpoint implements a long-poll pattern for receiving real-time mail notifications. When called, the request blocks (holds the connection open) until either new mail arrives or the specified timeout period expires. This is far more efficient than repeatedly calling the quick-check endpoint.

Long-Poll Pattern

This endpoint is designed for:

  • Real-time mail notification systems
  • Efficient server-push-style updates over HTTP
  • Reducing network overhead compared to frequent short polling
  • Desktop and mobile clients that need instant new-mail alerts
Poll vs. Check

Unlike the /api/qmail/net/beacon/peek endpoint which returns quickly with a 10-second timeout, this endpoint blocks for up to 10 minutes (600 seconds) by default. Use /poll when you want to be notified as soon as new mail arrives, and /check for quick non-blocking lookups.

Parameters

Parameter Type Required Description
timeout integer No Timeout in seconds for the long-poll. The request will block for at most this many seconds before returning. Default: 600. Maximum: 600 (10 minutes).

Response

Success Response Properties

success boolean
Indicates whether the poll completed successfully with new mail. true when new mail was found.
new_mail_count integer
The number of new mail notifications returned in this response.
total_remaining integer
The total number of notifications still remaining in the beacon queue after this response.
notifications array
Array of notification objects describing each new mail item.
timed_out boolean
true only when the RAIDA itself returned STATUS_TIMEOUT (245) after exhausting its server-side 15-minute long-poll cap. false in all other cases, including when rest_core's own client-side timeout param expires before the RAIDA responds. Distinguishes "RAIDA said no-mail-yet" from "RAIDA actually timed out."
raida_status integer
Wire-level status from the beacon RAIDA. 245 = STATUS_TIMEOUT (server-side cap reached). 0 = no error / no-mail-yet placeholder, or rest_core-side timeout fired before the RAIDA responded — these two cases are not currently distinguishable.
elapsed_ms integer
Wall-clock milliseconds the handler held the connection, measured from request entry to exit. Useful for verifying long-poll behavior in one call: elapsed_ms ≈ requested timeout on an empty mailbox proves the server didn't return early.
request_id string
16-char hex correlation id (64 bits of entropy). Also present on every log line emitted by this request as rid=<id>; grep "rid=<id>" main.log retrieves the full transaction.
Client-Side Timeout vs. Server-Side Timeout

The timeout query parameter is honored by rest_core only — it does not reach the beacon RAIDA. The RAIDA has its own hard-coded 15-minute long-poll cap (PING_TIMEOUT_SECONDS). When you request timeout=5:

  • rest_core opens a TCP connection to the RAIDA and waits up to 5 seconds for a response.
  • If the 5 seconds expire before the RAIDA replies, rest_core returns synthesized values: raida_status: 0, timed_out: false, and the actual elapsed_ms.
  • The RAIDA-side TCP socket remains parked in STATE_PING_ACTIVE for up to 15 minutes (or until inotify fires) regardless.

Only when the request runs the full 15+ minutes will raida_status: 245 and timed_out: true land in the response.

Notification Object Properties

file_guid string
Hexadecimal GUID identifying the incoming file.
sender_sn integer
The serial number of the sender.
timestamp integer
Unix timestamp (in seconds) when the notification was created.
tell_type integer
Numeric type code indicating the kind of notification (e.g., message, file transfer).
total_file_size integer
Total size of the incoming file in bytes.

Success Response Example (New Mail Arrived)

{
  "success": true,
  "request_id": "3c036987b62a40b2",
  "new_mail_count": 3,
  "total_remaining": 7,
  "timed_out": false,
  "raida_status": 0,
  "elapsed_ms": 84,
  "notifications": [
    {
      "file_guid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
      "sender_sn": 14582,
      "timestamp": 1735689800,
      "tell_type": 1,
      "total_file_size": 2048
    }
  ]
}

Client-Side Timeout Response Example (timeout=5, mailbox empty)

The rest_core's 5-second client-side window expired before the RAIDA responded. The RAIDA-side TCP socket is still parked in STATE_PING_ACTIVE waiting for the actual 15-minute cap. raida_status: 0 and timed_out: false here are placeholders, NOT a true server-side timeout.

{
  "success": true,
  "request_id": "3c036987b62a40b2",
  "new_mail_count": 0,
  "total_remaining": 0,
  "timed_out": false,
  "raida_status": 0,
  "elapsed_ms": 5028
}

Server-Side Timeout Response Example (timeout=0 or ≥900, mailbox empty)

To genuinely test the RAIDA's STATUS_TIMEOUT path, the rest_core's client-side timeout has to be longer than the RAIDA's 15-minute (PING_TIMEOUT_SECONDS) cap. Use curl -m 0 ".../qmail/net/beacon/ping" (no curl-side limit) and let the RAIDA's own cap fire.

{
  "success": true,
  "request_id": "8a2f3c91b7e0d4d2",
  "new_mail_count": 0,
  "total_remaining": 0,
  "timed_out": true,
  "raida_status": 245,
  "elapsed_ms": 900042
}

Error Response Example (RAIDA unreachable / network failure)

{
  "error": true,
  "message": "Poll timed out or failed",
  "code": 500,
  "request_id": "5c91a8b3d2e7f104",
  "detail": "RESULT_NETWORK_ERROR"
}

Interactive API Tester

Test this Endpoint

http://localhost:8082/api/qmail/net/beacon/ping?timeout=30
Long-Running Request

This request will block until new mail arrives or the timeout expires. The default test timeout is set to 30 seconds. Adjust as needed.

Examples

cURL

# Long-poll with default 600-second timeout
curl "http://localhost:8082/api/qmail/net/beacon/ping"

# Long-poll with a custom 60-second timeout
curl "http://localhost:8082/api/qmail/net/beacon/ping?timeout=60"

JavaScript (fetch)

async function pollForMail(timeoutSeconds = 600) {
    const url = 'http://localhost:8082/api/qmail/net/beacon/ping'
        + `?timeout=${timeoutSeconds}`;

    // Set a client-side abort timeout slightly longer than the server timeout
    const controller = new AbortController();
    const clientTimeout = setTimeout(() => controller.abort(), (timeoutSeconds + 10) * 1000);

    try {
        const response = await fetch(url, { signal: controller.signal });
        const result = await response.json();

        if (response.ok && result.success) {
            console.log(`New mail! ${result.new_mail_count} notification(s).`);
            console.log(`${result.total_remaining} more remaining in queue.`);
            result.notifications.forEach(n => {
                console.log(`  File: ${n.file_guid}, Sender SN: ${n.sender_sn}, Size: ${n.total_file_size} bytes`);
            });
        } else {
            console.log(`Poll ended: ${result.message} (HTTP ${result.code ?? response.status})`);
            if (result.detail) {
                console.log(`Detail: ${result.detail}`);
            }
        }

        return result;
    } catch (error) {
        if (error.name === 'AbortError') {
            console.log('Poll request timed out on client side.');
        } else {
            console.error('Poll error:', error);
        }
    } finally {
        clearTimeout(clientTimeout);
    }
}

// Continuous long-poll loop
async function startPolling() {
    while (true) {
        const result = await pollForMail(300);
        if (result && result.success) {
            // Process new mail notifications here
        }
        // Immediately re-poll (the server handles the wait)
    }
}

pollForMail(60);

Python

import requests

def poll_for_mail(timeout_seconds=600):
    """Long-poll for new mail notifications."""
    url = 'http://localhost:8082/api/qmail/net/beacon/ping'
    params = {'timeout': timeout_seconds}

    # Set a client-side timeout slightly longer than the server timeout
    response = requests.get(url, params=params, timeout=timeout_seconds + 10)
    result = response.json()

    if response.ok and result.get('success'):
        print(f"New mail! {result['new_mail_count']} notification(s).")
        print(f"{result['total_remaining']} more remaining in queue.")
        for n in result['notifications']:
            print(f"  File: {n['file_guid']}, Sender SN: {n['sender_sn']}, Size: {n['total_file_size']} bytes")
    else:
        print(f"Poll ended: {result['message']} (HTTP {result.get('code', response.status_code)})")
        if result.get('detail'):
            print(f"Detail: {result['detail']}")

    return result

# Continuous long-poll loop
def start_polling():
    while True:
        try:
            result = poll_for_mail(timeout_seconds=300)
            if result.get('success'):
                # Process new mail notifications here
                pass
        except requests.exceptions.Timeout:
            print('Poll request timed out, restarting...')
        except requests.exceptions.RequestException as e:
            print(f'Poll error: {e}')

poll_for_mail(timeout_seconds=60)

Important Notes

Blocking Request

This is a long-poll endpoint. The HTTP connection remains open until new mail arrives or the timeout expires. Ensure your HTTP client is configured with an appropriate timeout that exceeds the server-side timeout parameter to avoid premature client-side disconnections.

Timeout Behavior

The timeout parameter controls how long the server will wait for new mail. The default and maximum value is 600 seconds (10 minutes). If no new mail arrives within the timeout, the server responds with success: false and the message "Poll timed out or failed". Simply re-issue the request to continue waiting.

Client Timeout Configuration

Always set your client-side HTTP timeout to be slightly longer than the server-side timeout value. For example, if you set timeout=300, configure your HTTP client to wait at least 310 seconds. This prevents the client from disconnecting before the server has a chance to respond.

Continuous Polling Pattern

For real-time notifications, implement a continuous loop: call /poll, process any results, then immediately call /poll again. The server handles the waiting, so there is no need to add a sleep delay between requests.