How-to guide
Queuey Edge — durable publishing from unreliable networks
For producers where the network is a fact of life, not a guarantee — factories, kiosks, vehicles, ships, on-prem services. One PublishAsync call durably accepts the event on the local machine; Queuey owns the delivery mechanics from there.
The contract
When PublishAsync returns, the event is committed to a local durable store (SQLite, fsync'd) under a permanent transfer identity. Queuey then handles transfer to Queuey Cloud, retries with backoff and jitter, reconnect, lost-acknowledgement resolution, idempotent resend, recovery across process and machine restarts, and backlog draining — in order. Kill the network for two days: events accumulate durably and drain when it returns. A retry, however late, can never become a second logical event.
// ❌ What Queuey.Edge exists to delete from your codebase
if (!await queuey.IsOnline())
{
await localDb.Save(evt);
ScheduleRetry(...);
}Set it up once
Install the Queuey.Edge NuGet package, register it, and mint a publish-only, tenant-scoped API key in the console (Developer → API keys) — a key on an edge machine should never be able to do anything but publish to its own workspace.
// Program.cs — any .NET host (worker service, ASP.NET, console)
builder.Services.AddQueueyEdge(o =>
{
o.ApiKey = "qak_…"; // a PUBLISH-ONLY, tenant-scoped Edge key
o.TenantPublicId = "ten_…";
// Optional: o.Storage.Path = "/var/lib/myapp/queuey/spool.db";
});Then publish — that's the whole app
// The whole delivery story in application code:
public sealed class SensorService(IQueueyPublisher queuey)
{
public async Task OnReading(Reading reading) =>
await queuey.PublishAsync("sensor-readings", reading, new PublishOptions
{
EventType = "temperature.updated",
GroupKey = reading.DeviceId, // ordering lane
OccurredAtUtc = reading.ReadAtUtc, // honest history when a backlog drains
});
}OccurredAtUtc and a backlog drained on Wednesday still reads as Monday in the console — Queuey stores the occurrence time separately from the receive time (X-Queuey-Occurred-At).What can actually go wrong at the call
PublishAsync throws only for conditions that exist before Queuey takes responsibility: missing configuration, a payload that cannot be serialized or exceeds your local cap, a full local store (QueueySpoolFullException — the honest backpressure of lossless retention), or a corrupt store awaiting explicit operator recovery. Network state, Queuey Cloud availability and HTTP errors never surface at the call site.
spool.db* from antivirus and file-copy backups. Default 512 MB spool ≈ a year of offline autonomy at 1 event/minute × 1 KB.Queuey exposes health — you monitor it
Edge publishes an OpenTelemetry meter and an in-process snapshot for whatever monitoring you already run (Azure Monitor, Datadog, Prometheus, SCADA). If you wire exactly one alert, use oldest_age_seconds: it grows when anything has been stopping transfer, and Edge resumes by itself when the cause clears.
Meter: Queuey.Edge (OpenTelemetry)
queuey.edge.spool.pending events accepted locally, not yet transferred
queuey.edge.spool.oldest_age_seconds ← THE signal worth alerting on
queuey.edge.spool.quarantined permanently rejected, awaiting operator retry/discard
queuey.edge.cloud.last_contact_seconds seconds since last successful transfer
queuey.edge.state 0 Healthy · 1 Backlogged · 2 RequiresAction
3 StorageFull · 4 StorageFaulted
queuey.edge.transfer.accepted{replayed} / transfer.failed{class,reason}Operator verbs
The queuey CLI operates a spool file directly, alongside a running host. Discard reaches only quarantined events, and data loss is always an explicit, acknowledged decision — never Queuey's default.
queuey edge status --spool <path> [--json]
queuey edge retry --spool <path> (--id N | --all) # after fixing a quarantine cause
queuey edge discard --spool <path> --id N # explicit, logged operator decision
queuey edge recover --spool <path> # salvage a faulted spool
queuey edge reset --spool <path> --accept-data-loss # start clean; old file preserved- Runnable demo (kill Queuey, watch events survive and drain): samples/Queuey.Edge.Demo
- Sending from a reliably connected service instead? The plain quickstart (one HTTP POST) is all you need.
- What happens after Cloud accepts: reliable delivery and operating deliveries.