/api/qmail/net/beacon/ping
GETLong-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.
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
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
true when new mail was found.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."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 ≈ requested timeout on an empty mailbox proves the server didn't return early.rid=<id>; grep "rid=<id>" main.log retrieves the full transaction.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 actualelapsed_ms. - The RAIDA-side TCP socket remains parked in
STATE_PING_ACTIVEfor 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
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
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
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.
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.
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.
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.