Skip to content
GigadriveDocs

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: 2m

You 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 functionThe message
ReturnsIs acknowledged and deleted
Throws RetryLaterError(delay)Is delivered again after delay, without spending an attempt
Throws NonRetryableErrorMoves to the dead-letter list now, with your message as the reason
Throws anything elseIs 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:

  1. Reject the delivery when t is more than five minutes away from your clock.
  2. Hash the raw request body with SHA-256, as lowercase hex.
  3. Join t, the values of x-gigadrive-queue-name, x-gigadrive-queue-message-id and x-gigadrive-queue-attempt, and the body hash, in that order, with . between them.
  4. Compute the HMAC-SHA256 of that string, keyed with the UTF-8 bytes of GIGADRIVE_QUEUE_SIGNING_SECRET exactly as the variable holds it. Do not hex-decode the secret.
  5. Compare the result with v1 in 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

HeaderValue
x-gigadrive-queue-nameThe queue name
x-gigadrive-queue-message-idThe message id, the same on every delivery of the message
x-gigadrive-queue-attempt1 on the first delivery
x-gigadrive-queue-created-atWhen the message was sent, in Unix milliseconds
x-gigadrive-queue-expires-atWhen the message's retention ends, in Unix milliseconds
x-gigadrive-queue-deadlineWhen this delivery's lease ends, in Unix milliseconds
x-gigadrive-queue-group-keyThe ordering group, when the message has one
x-gigadrive-queue-deduplication-keyThe deduplication key, when the message has one
x-gigadrive-queue-deployment-idThe deployment receiving this delivery
x-gigadrive-queue-deployment-fallback1 when the message's pinned deployment no longer serves it
x-gigadrive-queue-signaturet=<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:

ResponseThe message
2xxIs 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-letterMoves to the dead-letter list now
Another status, a redirect, a timeout or a connection errorIs 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

SettingEffect
concurrencyMost deliveries in flight at once for the queue, 100 by default and up to 1,000
rateLimitMost deliveries started per period, such as { count: 10, period: 1s } for a slow API
PauseStops 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.