Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Azure Queue Laravel Laravel Package

squigg/azure-queue-laravel

A Laravel queue driver for Microsoft Azure Queue Storage. Dispatch and process jobs through Azure Storage Queues using Laravel's familiar queue workflows, with connection and queue settings configured through Laravel's queue configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

What this package is: a queue driver that plugs Azure Queue Storage into Laravel's queue system. It implements Illuminate\Contracts\Queue\Queue so dispatch(), dispatchSync(), later(), Bus::batch(), Queue::fake(), and queue:work all behave the way you expect — the only thing that changes is where the payload physically lives.

Requirements

  • PHP 8.2+ and Laravel 12.x for v12.0.0 (the package major tracks the Laravel major — see Gotchas).
  • An Azure Storage account, or Azurite for local development.

Steps

  1. Install and pin to the Laravel major you're running:

    composer require squigg/azure-queue-laravel:^12.0
    

    The service provider is auto-discovered and registers the azure queue driver — you do not need to add it to config/app.php.

  2. Add a connection in config/queue.php:

    'azure' => [
        'driver' => 'azure',
        'connection_string' => env('AZURE_STORAGE_CONNECTION_STRING'),
        'queue' => env('AZURE_QUEUE_NAME', 'default'),
        'timeout' => 60,        // visibility timeout for a reserved message
        'retry_after' => 90,    // must exceed worker --timeout
        'ttl' => 604800,        // message time-to-live (seconds)
    ],
    
  3. .env:

    QUEUE_CONNECTION=azure
    AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net
    AZURE_QUEUE_NAME=default
    
  4. First use case — dispatch a job and watch it land:

    php artisan make:job SendWelcomeEmail
    php artisan tinker
    >>> SendWelcomeEmail::dispatch($user);
    
    php artisan queue:work azure --queue=default --tries=3 --timeout=60
    

Where to look first in the source

  • src/AzureQueueServiceProvider.php — driver registration and the exact config keys your version actually reads.
  • src/AzureConnector.php / src/AzureQueue.php — push, pop, later, clear, and how retry_after is translated into the SDK's visibility timeout.
  • src/AzureJob.php — how delete, release, and attempts() map onto Azure message operations.

The config key names above are worth double-checking against the README for your pinned version before you commit — this is the single most common first-run stumble.

Implementation Patterns

Route work by queue, not by connection. One Azure connection is usually enough; use queues to separate workload classes:

SendWelcomeEmail::dispatch($user)->onQueue('notifications');
GenerateInvoicePdf::dispatch($order)->onQueue('heavy');

Then run dedicated workers per queue so a slow queue can't starve a fast one:

php artisan queue:work azure --queue=notifications --tries=3 --timeout=30
php artisan queue:work azure --queue=heavy --tries=1 --timeout=300

Delayed jobs and backoff use Azure's visibility timeout under the hood (later() pushes with an initial invisibility window). $job->backoff() and $job->retryUntil() work, but every retry re-reserves the message — keep backoff values realistic relative to your retry_after.

Local development with Azurite (the Azure Storage emulator) keeps developers off the shared cloud queue:

# docker-compose.yml
azurite:
  image: mcr.microsoft.com/azure-storage/azurite
  command: azurite-queue --queueHost 0.0.0.0 --queuePort 10001 --loose
  ports: ['10001:10001']
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;QueueEndpoint=http://azurite:10001/devstoreaccount1;

Azurite also makes a great CI service container — the newly added GitHub Actions workflow in the repo (v12.0.0) is a useful reference for wiring that up.

Testing. Queue::fake() and assertPushed() work unchanged because the driver is behind the standard contract — the vast majority of your tests never touch Azure at all. Reserve Azurite for the handful of integration tests that assert a job actually round-trips.

Deployment shape. This driver does not work with Horizon (Horizon is Redis-only). Run workers under Supervisor, systemd, or a container orchestrator (Container Apps, AKS, ECS). Store the connection string in Key Vault and inject it as an env var — never commit it.

Scale-out is trivial because Azure Queue is a shared external broker: add worker processes and they pull independently. There's no central scheduler to configure, which is the main operational win over database queues.

Gotchas and Tips

Version pinning is the big one in this release. v12.0.0 is a major bump that exists primarily for Laravel 12 compatibility (plus CI hardening). The version number tracks the Laravel major, so a Laravel 11 app must stay on ^11.0 — composer require squigg/azure-queue-laravel without a constraint can silently pull a version your framework can't load. Add the constraint explicitly and commit the lockfile.

The 64 KB message ceiling is the pitfall that bites hardest. Azure caps a queue message at 64 KB, and the payload is base64-encoded on the wire, so the effective budget is roughly 48 KB of JSON. A job carrying a large Eloquent model graph or a fat array will throw at dispatch time — often in production under a specific data shape, not in tests. Pass IDs and re-hydrate in handle():

// Don't
ProcessReport::dispatch($hugeCollection);
// Do
ProcessReport::dispatch($report->id);

retry_after must exceed --timeout. Azure has no "reserve" concept — a message becomes visible again when its visibility timeout expires, whether or not the worker finished. Set retry_after below your job timeout and long-running jobs get processed twice concurrently. Set it comfortably above and you're safe; set it far too high and a crashed worker strands the job.

Azure's poison queue is invisible to Laravel's failure tooling. After the max dequeue count (default 5), Azure moves the message to {queue}-poison — it never reaches failed_jobs, so queue:failed, queue:retry, and queue:forget won't help. Exceptions thrown inside handle() are recorded normally by the worker; it's the killed-worker / repeatedly-crashing case that lands in the poison queue. Add an alert on poison queue depth — that's your real "jobs are silently dying" signal.

At-least-once delivery, no ordering guarantee. Azure Queue is best-effort FIFO; retries and visibility timeouts can reorder and duplicate. Make handle() idempotent — guard with a unique key or a Cache::lock() around the side effect.

Queue names are constrained: lowercase alphanumerics and hyphens only, 3–63 characters, no leading/trailing hyphen, no consecutive hyphens. SendWelcomeEmail as a queue name won't work; send-welcome-email will. Also remember each queue must exist — create it in the portal, CLI, or at deploy time, otherwise pushes fail.

Message TTL defaults to 7 days. Jobs sitting in a backlog longer than that vanish. If you run low-volume queues with long delays, set ttl explicitly rather than relying on the default.

Debugging. In Storage Explorer or the portal, the message body is base64-encoded JSON — decode it before reading the job class name. The message's dequeue_count property tells you how many times a job has been picked up, which is the fastest way to distinguish "job is failing" from "job was never dispatched." Enable SDK logging temporarily if you suspect auth or endpoint issues.

Config caching. After changing any of these values, run php artisan config:cache (and config:clear locally) — a stale cached config pointing at the old connection string produces confusing auth failures in production.

Extension points. If you need custom behavior (per-job TTL, custom message metadata, a different SDK client), the supported route is Queue::extend('azure', ...) with your own connector, or subclass AzureQueue and swap the driver binding in a service provider. Before that, check whether src/AzureQueue.php already exposes what you need — clear(), size(), and later() overrides are the usual customization points.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json