Push consumers
Receive queue messages as signed requests to your deployment, and control acknowledgement, retries, dead letters and throughput.
A push queue sends each message as a POST to its consumer path on your deployment, so a Function processes it like any other request. Use push unless your consumer runs outside Gigadrive Network.
Set the consumer path
Declare the path in gigadrive.yaml, and the queue is created or updated when the deployment is provisioned:
services:
queues:
emails:
consumer: /api/queues/emails
maxAttempts: 5
visibilityTimeout: 2mYou can also set Consumer path on the queue's Settings tab in the console, or call emails.ensure({ consumerPath: '/api/queues/emails' }) from code. Clearing the path, or consumer: null in gigadrive.yaml, turns the queue into a pull queue, and messages wait until something receives them.
Write the handler
handler returns a function that takes a Request and returns a Response, which is the shape of a Next.js route handler and of most modern frameworks. It verifies the delivery's signature, decodes the payload, and calls your function with it. Inside a deployed Function, queue() needs no configuration: it reads the signing secret the platform injects, and authenticates any API call with the Function's own credential, described in OIDC federation.
// app/api/queues/emails/route.ts
import { NonRetryableError, queue, RetryLaterError } from '@gigadrive/sdk';
const emails = queue<{ to: string; template: string }>('emails');
export const POST = emails.handler(async (email, { attempt, messageId }) => {
if (!templates.has(email.template)) {
throw new NonRetryableError(`Unknown template ${email.template}`);
}
if (await mailer.isThrottled()) {
throw new RetryLaterError('1m');
}
await mailer.send(email, { idempotencyKey: messageId });
});What your function does decides what happens to the message:
| Your function | The message |
|---|---|
| Returns | Is acknowledged and deleted |
Throws RetryLaterError(delay) | Is delivered again after delay, without spending an attempt |
Throws NonRetryableError | Moves to the dead-letter list now, with your message as the reason |
| Throws anything else | Is retried with backoff, and the error is recorded on the message |
Use the message id as an idempotency key for side effects, because a message can be delivered more than once.
Validate payloads
Pass validate to check every payload before your handler runs, for example with a Zod schema. A payload that fails validation is dead-lettered, since retrying cannot fix it.
import { queue } from '@gigadrive/sdk';
import { z } from 'zod';
const Email = z.object({ to: z.string().email(), template: z.string() });
const emails = queue('emails', { validate: Email.parse });Delivery signatures
Every push delivery carries x-gigadrive-queue-signature, an HMAC over the queue name, message id, attempt and body, made with a secret unique to the environment. The platform injects that secret into your Functions as GIGADRIVE_QUEUE_SIGNING_SECRET, and handler answers 401 to any request whose signature is missing, wrong or more than five minutes old.
Without GIGADRIVE_QUEUE_SIGNING_SECRET, as in local development, handler refuses every delivery. Pass allowUnsigned: true to accept unsigned requests while you develop, and never in a deployment. To verify in a server the SDK does not wrap, call verifyQueueSignature(header, { secret, queue, messageId, attempt, body }) with the raw body.
Verify a signature without the SDK
The header value has the form t=<timestamp>,v1=<signature>, where t is the signing time in Unix milliseconds and v1 is a lowercase hex HMAC-SHA256. To check it:
- Reject the delivery when
tis more than five minutes away from your clock. - Hash the raw request body with SHA-256, as lowercase hex.
- Join
t, the values ofx-gigadrive-queue-name,x-gigadrive-queue-message-idandx-gigadrive-queue-attempt, and the body hash, in that order, with.between them. - Compute the HMAC-SHA256 of that string, keyed with the UTF-8 bytes of
GIGADRIVE_QUEUE_SIGNING_SECRETexactly as the variable holds it. Do not hex-decode the secret. - Compare the result with
v1in constant time.
This Python function implements those steps:
import hashlib
import hmac
import os
import time
def verify_queue_signature(headers: dict[str, str], body: bytes) -> bool:
fields = dict(part.split("=", 1) for part in headers.get("x-gigadrive-queue-signature", "").split(",") if "=" in part)
timestamp, signature = fields.get("t", ""), fields.get("v1", "")
if not timestamp.isdigit() or abs(time.time() * 1000 - int(timestamp)) > 5 * 60 * 1000:
return False
payload = ".".join(
[
timestamp,
headers["x-gigadrive-queue-name"],
headers["x-gigadrive-queue-message-id"],
headers["x-gigadrive-queue-attempt"],
hashlib.sha256(body).hexdigest(),
]
)
secret = os.environ["GIGADRIVE_QUEUE_SIGNING_SECRET"].encode()
expected = hmac.new(secret, payload.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Pass the headers with lowercase names and the body bytes before any parsing.
Delivery headers
| Header | Value |
|---|---|
x-gigadrive-queue-name | The queue name |
x-gigadrive-queue-message-id | The message id, the same on every delivery of the message |
x-gigadrive-queue-attempt | 1 on the first delivery |
x-gigadrive-queue-created-at | When the message was sent, in Unix milliseconds |
x-gigadrive-queue-expires-at | When the message's retention ends, in Unix milliseconds |
x-gigadrive-queue-deadline | When this delivery's lease ends, in Unix milliseconds |
x-gigadrive-queue-group-key | The ordering group, when the message has one |
x-gigadrive-queue-deduplication-key | The deduplication key, when the message has one |
x-gigadrive-queue-deployment-id | The deployment receiving this delivery |
x-gigadrive-queue-deployment-fallback | 1 when the message's pinned deployment no longer serves it |
x-gigadrive-queue-signature | t=<ms>,v1=<hex> |
The message's own headers and its content type arrive as ordinary request headers.
Use another language or framework
Any endpoint can consume a push queue. Respond to steer the message:
| Response | The message |
|---|---|
2xx | Is acknowledged |
2xx with x-gigadrive-queue-retry-after: <seconds> | Is delivered again after that many seconds (at most a year), no attempt spent |
Any status with x-gigadrive-queue-action: dead-letter | Moves to the dead-letter list now |
| Another status, a redirect, a timeout or a connection error | Is retried with backoff; a Retry-After header, in seconds or as a date, sets the delay, up to a day |
With x-gigadrive-queue-action: dead-letter, add x-gigadrive-queue-error to record your reason on the message. For any other failure, the platform records HTTP <status> and the first 512 bytes of the response body, and ignores x-gigadrive-queue-error.
Retries and dead letters
A failed delivery is retried with exponential backoff and jitter: the delay starts at 5 seconds, doubles with each attempt, and stops growing at 15 minutes. After maxAttempts deliveries, 10 by default and at most 100, the message moves to the dead-letter list, or is deleted when the queue keeps no dead letters.
On the queue's Messages tab, the Dead letters filter lists them with their last error. Retry one, or select Retry all to send every dead letter back with fresh attempts. From code, call emails.redrive().
Timeouts
A delivery must answer within the queue's visibility timeout minus one second, so the request ends before the lease does. The visibility timeout is 60 seconds by default and up to 12 hours, and a push delivery always gets at least 5 seconds, even when the queue's setting is lower. A delivery that runs longer counts as a failure and is retried, so set the timeout above your handler's longest run and below your Function's maximum duration.
Control throughput
| Setting | Effect |
|---|---|
concurrency | Most deliveries in flight at once for the queue, 100 by default and up to 1,000 |
rateLimit | Most deliveries started per period, such as { count: 10, period: 1s } for a slow API |
| Pause | Stops deliveries while sends keep queueing; Pause on the queue's page, or emails.pause() |
Which deployment receives a message
Messages go to the deployment currently serving the environment, so a promotion or a rollback redirects deliveries within about ten seconds. A message pinned with deploymentId goes to that deployment while it still serves the environment. When the environment has nothing deployed, messages wait: they are not delivered, spend no attempts and cost no operations until a deployment is live.
