XSCREENSAVER / 3D PIPES

[readonly] markdown buffer

Queueable Apex That Survives Retries

Jun 16, 2026 · 7 min read

Queueable Apex makes work easy to start. Reliability begins after System.enqueueJob() returns.

A callout can time out, a record can lock, a user can click twice, or an operator can retry a job whose remote effect completed before Salesforce recorded success.

The dangerous assumption is that one enqueue produces one business effect.

Important asynchronous work should be designed for more than one attempt.

Protect the business operation

Suppose a Queueable submits an order to a fulfilment API, then updates the Salesforce order.

public void execute(QueueableContext context) {
    Order__c orderRecord = loadOrder(orderId);
    fulfilmentClient.submit(orderRecord);
    orderRecord.Status__c = 'Submitted';
    update orderRecord;
}

If the remote call succeeds and the update fails, a retry can submit the order twice.

traceview://queue-retry
job identity onlyduplicate effect
  1. 01submit fulfilmentshipment created
  2. 02update Salesforcerecord lock
  3. 03retry jobnew attempt
  4. 04submit fulfilmentsecond shipment
business effects2 shipments
stable operation keyrepeatable delivery
idempotency keyfulfil-order:801…:v1
  1. 01claim operationattempt 1
  2. 02remote accepts keyshipment created
  3. 03retry same keyexisting result
  4. 04record completioncheckpoint saved
business effects1 shipment
A Salesforce retry repeats delivery. Without a stable operation key it creates a second shipment; with one, the remote service returns the existing result and the job records completion.

The solution starts with a stable business idempotency key, such as fulfil-order:801xx0000001234:v1. Send it to an API that supports idempotency or agree another deduplication contract. A new random value on every attempt defeats the point.

The Salesforce job ID remains useful operational data. It is not the identity of the business operation.

Make progress durable

Significant jobs need a work record containing the operation key, target, status, attempt count, next attempt time, last error, external reference, and completion time.

That record answers what AsyncApexJob cannot: which operation this was, whether a later attempt succeeded, what happened remotely, and whether somebody must intervene.

Before performing work, claim the record and exit safely when it is already complete.

Async_Work__c workItem = [
    SELECT Id, Status__c, Attempt_Count__c, Operation_Key__c
    FROM Async_Work__c
    WHERE Id = :workItemId
    FOR UPDATE
];

if (workItem.Status__c == 'Completed') {
    return;
}

FOR UPDATE protects the Salesforce transaction. The idempotency key still protects the external effect.

traceview://queue-work-record
Async_Work__cfulfil-order:801…:v1
target
Order 801…1234
attempt
1 / 3
external ref
next attempt
14:02:30
  1. 01callout timed outretryable
  2. 02backoff scheduled30 seconds
  3. 03business operation completedSHIP-48291
monitorretry scheduledbusiness outcome, not merely job status
The durable work record survives individual Queueable attempts. It carries retry timing, the external reference and the final business outcome after the original job has gone away.

Keep units of work small and resumable: one external command, cursor page, repair partition, or group that genuinely shares a transaction. A job that processes everything and records progress only at the end is difficult to retry safely.

Retry only what can recover

Rate limits, temporary server errors, record locks, and safe network timeouts may justify another attempt. Invalid configuration, missing business data, and unsupported requests usually do not.

Blindly retrying permanent failures consumes capacity and delays intervention.

Every policy needs a cap, increasing delay, and a visible terminal state. Queueable delay features provide mechanics; the application still decides whether retrying is correct.

A transaction finaliser can observe escaped failures and schedule controlled continuation. It cannot make a non-idempotent operation safe.

Monitor the outcome, not the job

An Apex job marked Completed only proves that Apex finished. It does not prove the customer's order reached its required state.

Monitoring should connect the job ID, operation key, attempt, target record, external reference, and final business status.

Test duplicate delivery, already-completed work, transient failure followed by success, permanent failure, exhausted attempts, and failure after a remote response but before local completion.

Queueable is a delivery mechanism, not an exactly-once guarantee.

Once work is treated as repeatable delivery, the design becomes clearer: stable identity, durable state, bounded retries, resumable checkpoints, and monitoring that speaks the language of the business operation.