PHPackages                             dealnews/inngest-php-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Queues &amp; Workers](/categories/queues)
4. /
5. dealnews/inngest-php-sdk

ActiveLibrary[Queues &amp; Workers](/categories/queues)

dealnews/inngest-php-sdk
========================

Unofficial PHP SDK for Inngest - Build event-driven workflows with durable execution

0.1.14(2mo ago)41.6k↓22%BSD-3-ClausePHPPHP ^8.1

Since Nov 10Pushed 1mo agoCompare

[ Source](https://github.com/dealnews/inngest-php-sdk)[ Packagist](https://packagist.org/packages/dealnews/inngest-php-sdk)[ RSS](/packages/dealnews-inngest-php-sdk/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (12)Versions (19)Used By (0)

Inngest PHP SDK
===============

[](#inngest-php-sdk)

Unofficial PHP SDK for [Inngest](https://www.inngest.com) - Build event-driven workflows with durable execution.

Installation
------------

[](#installation)

```
composer require dealnews/inngest-php-sdk
```

Requirements
------------

[](#requirements)

- PHP 8.1 or higher
- ext-json
- ext-hash

Features
--------

[](#features)

- ✅ **Event-driven workflows** - Trigger functions from events
- ✅ **Durable execution** - Automatic retries and step memoization
- ✅ **Step functions** - Break work into retriable blocks
- ✅ **Sleep &amp; delays** - Pause execution for minutes, hours, or days
- ✅ **Wait for events** - Coordinate across async workflows
- ✅ **Function invocation** - Call other Inngest functions
- ✅ **Cron triggers** - Schedule recurring tasks
- ✅ **Concurrency control** - Limit parallel execution
- ✅ **Priority queues** - Dynamic execution ordering
- ✅ **Debounce** - Delay execution until events settle
- ✅ **Rate limiting** - Hard limit on function runs per time period
- ✅ **Throttling** - FIFO queueing with burst support
- ✅ **Singleton** - Ensure only one run executes at a time
- ✅ **Dev mode** - Local development with Inngest dev server
- ✅ **Type safety** - Full PHP 8.1+ type declarations

Quick Start
-----------

[](#quick-start)

### 1. Create an Inngest Client

[](#1-create-an-inngest-client)

```
use DealNews\Inngest\Client\Inngest;

$client = new Inngest('my-app');
```

### 2. Define a Function

[](#2-define-a-function)

```
use DealNews\Inngest\Function\InngestFunction;
use DealNews\Inngest\Function\TriggerEvent;

$function = new InngestFunction(
    id: 'hello-world',
    handler: function ($ctx) {
        $name = $ctx->getEvent()->getData()['name'] ?? 'World';
        return ['message' => "Hello, {$name}!"];
    },
    triggers: [
        new TriggerEvent('demo/hello')
    ],
    name: 'Hello World Function'
);

$client->registerFunction($function);
```

### 3. Serve Functions

[](#3-serve-functions)

```
use DealNews\Inngest\Http\ServeHandler;

$handler = new ServeHandler($client, '/api/inngest');

// In your framework (e.g., Laravel, Symfony):
$response = $handler->handle(
    method: $_SERVER['REQUEST_METHOD'],
    path: $_SERVER['REQUEST_URI'],
    headers: getallheaders(),
    body: file_get_contents('php://input'),
    query: $_GET
);

http_response_code($response['status']);
foreach ($response['headers'] as $key => $value) {
    header("{$key}: {$value}");
}
echo $response['body'];
```

### 4. Send Events

[](#4-send-events)

```
use DealNews\Inngest\Event\Event;

$client->send(new Event(
    name: 'demo/hello',
    data: ['name' => 'PHP Developer']
));
```

Using Steps
-----------

[](#using-steps)

Steps enable you to break your function into retriable blocks:

```
use DealNews\Inngest\Function\InngestFunction;
use DealNews\Inngest\Function\TriggerEvent;

$function = new InngestFunction(
    id: 'process-order',
    handler: function ($ctx) {
        $step = $ctx->getStep();

        // Each step is individually retriable
        $order = $step->run('fetch-order', function () use ($ctx) {
            return fetchOrder($ctx->getEvent()->getData()['order_id']);
        });

        $step->run('charge-customer', function () use ($order) {
            return chargeCustomer($order);
        });

        // Sleep for a duration
        $step->sleep('wait-for-fulfillment', '1h');

        $step->run('send-confirmation', function () use ($order) {
            return sendConfirmationEmail($order);
        });

        return ['status' => 'complete'];
    },
    triggers: [new TriggerEvent('order/created')]
);
```

### Wait for Events

[](#wait-for-events)

```
$payment = $step->waitForEvent(
    id: 'wait-for-payment',
    event: 'payment/completed',
    timeout: '1h',
    if: 'event.data.order_id == async.data.order_id'
);
```

### Invoke Other Functions

[](#invoke-other-functions)

```
$result = $step->invoke(
    id: 'call-function',
    function_id: 'my-app-other-function',
    payload: ['data' => ['foo' => 'bar']]
);
```

Configuration
-------------

[](#configuration)

The SDK uses environment variables for configuration:

```
# Required for production
INNGEST_SIGNING_KEY=signkey-prod-xxxxx
INNGEST_EVENT_KEY=your-event-key

# Optional
INNGEST_SIGNING_KEY_FALLBACK=signkey-prod-yyyyy
INNGEST_ENV=production
INNGEST_DEV=1  # Enable dev server mode
INNGEST_API_BASE_URL=https://api.inngest.com
INNGEST_EVENT_API_BASE_URL=https://inn.gs
INNGEST_SERVE_ORIGIN=https://yourapp.com
INNGEST_SERVE_PATH=/api/inngest
INNGEST_LOG_LEVEL=debug
```

Or configure programmatically:

```
use DealNews\Inngest\Config\Config;

$config = new Config(
    event_key: 'your-event-key',
    signing_key: 'signkey-prod-xxxxx',
    is_dev: false
);

$client = new Inngest('my-app', $config);
```

Error Handling
--------------

[](#error-handling)

### Non-Retriable Errors

[](#non-retriable-errors)

```
use DealNews\Inngest\Error\NonRetriableError;

$function = new InngestFunction(
    id: 'validate-data',
    handler: function ($ctx) {
        if (!isValid($ctx->getEvent()->getData())) {
            throw new NonRetriableError('Invalid data');
        }
        return ['status' => 'ok'];
    },
    triggers: [new TriggerEvent('data/received')]
);
```

### Retry After Specific Time

[](#retry-after-specific-time)

```
use DealNews\Inngest\Error\RetryAfterError;

throw new RetryAfterError('Rate limited', retry_after: 60); // Retry after 60 seconds
```

Cron Triggers
-------------

[](#cron-triggers)

```
use DealNews\Inngest\Function\TriggerCron;

$function = new InngestFunction(
    id: 'daily-report',
    handler: function ($ctx) {
        generateDailyReport();
        return ['status' => 'complete'];
    },
    triggers: [
        new TriggerCron('0 0 * * *') // Every day at midnight
    ]
);
```

Concurrency Control
-------------------

[](#concurrency-control)

Limit how many steps can run simultaneously across all function runs. Useful for rate-limiting external APIs, managing resources, or preventing overwhelming downstream services.

### Basic Limit

[](#basic-limit)

```
use DealNews\Inngest\Function\Concurrency;

$function = new InngestFunction(
    id: 'process-data',
    handler: function ($ctx) {
        // Function logic
    },
    triggers: [new TriggerEvent('data/process')],
    concurrency: [
        new Concurrency(limit: 10) // Max 10 concurrent steps
    ]
);
```

### Per-User Limits

[](#per-user-limits)

```
// Limit to 2 concurrent runs per user
$function = new InngestFunction(
    id: 'user-task',
    handler: function ($ctx) {
        // Process user-specific task
    },
    triggers: [new TriggerEvent('user/task')],
    concurrency: [
        new Concurrency(
            limit: 2,
            key: 'event.data.user_id' // Group by user ID
        )
    ]
);
```

### Multi-Level Limits

[](#multi-level-limits)

```
// Set both regional and account-wide limits
$function = new InngestFunction(
    id: 'process-orders',
    handler: function ($ctx) {
        // Process order
    },
    triggers: [new TriggerEvent('order/created')],
    concurrency: [
        // Limit per region
        new Concurrency(
            limit: 5,
            key: 'event.data.region',
            scope: 'fn' // Per function (default)
        ),
        // Overall account limit
        new Concurrency(
            limit: 100,
            scope: 'account' // Across all environments
        )
    ]
);
```

### Concurrency Options

[](#concurrency-options)

- **limit**: Maximum concurrent steps (0 = unlimited)
- **key**: Expression to group concurrency (e.g., `event.data.user_id`, `event.data.region`)
- **scope**: Where the limit applies
    - `fn` (default): Per function
    - `env`: Per environment (production, staging, etc.)
    - `account`: Across entire account

**Heads-up:** Maximum of 2 concurrency configurations per function.

See [examples/concurrency.php](examples/concurrency.php) for more examples.

Priority
--------

[](#priority)

Dynamically prioritize function runs based on event data. Higher priority runs execute ahead of lower priority ones within the same function queue.

### Basic Priority

[](#basic-priority)

```
use DealNews\Inngest\Function\Priority;

$function = new InngestFunction(
    id: 'process-task',
    handler: function ($ctx) {
        // Function logic
    },
    triggers: [new TriggerEvent('task/process')],
    priority: new Priority(
        run: 'event.data.priority' // Use priority from event
    )
);
```

### Conditional Priority

[](#conditional-priority)

```
// Prioritize enterprise customers
$function = new InngestFunction(
    id: 'ai-generate-summary',
    handler: function ($ctx) {
        // Generate AI summary
    },
    triggers: [new TriggerEvent('ai/summary.requested')],
    priority: new Priority(
        // Enterprise accounts run up to 120 seconds ahead
        run: 'event.data.account_type == "enterprise" ? 120 : 0'
    )
);
```

### Delayed Priority

[](#delayed-priority)

```
// Delay free tier users
$function = new InngestFunction(
    id: 'process-report',
    handler: function ($ctx) {
        // Generate report
    },
    triggers: [new TriggerEvent('report/generate')],
    priority: new Priority(
        // Free plan users delayed by 60 seconds
        run: 'event.data.plan == "free" ? -60 : 0'
    )
);
```

### Priority Options

[](#priority-options)

- **run**: CEL expression that returns an integer priority factor
    - Range: `-600` to `600` seconds (enforced by Inngest)
    - Positive values: Run ahead of jobs enqueued up to N seconds ago
    - Negative values: Delay execution by N seconds
    - `0`: No priority (default queue position)

**How it works:** When a function run is enqueued, Inngest evaluates the expression using the event data. The result adjusts the run's position in the queue relative to other pending runs.

**Heads-up:**

- Most useful when combined with concurrency limits (jobs wait in queue)
- Invalid expressions evaluate to `0` (no priority)
- Out-of-range values are automatically clipped by Inngest

See [Inngest Priority Documentation](https://www.inngest.com/docs/reference/functions/run-priority) for more details.

Debounce
--------

[](#debounce)

Delay function execution until events stop arriving for a specified period. Prevents wasted work when functions might be triggered rapidly in succession (user input, webhook floods, frequent updates).

The function runs once using the **last event** received as input data.

### Basic Debounce

[](#basic-debounce)

```
use DealNews\Inngest\Function\Debounce;

$function = new InngestFunction(
    id: 'process-user-input',
    handler: function ($ctx) {
        $text = $ctx->getEvent()->getData()['text'];
        // Process final input after user stops typing
        return saveUserInput($text);
    },
    triggers: [new TriggerEvent('user/input')],
    debounce: new Debounce(
        period: '30s' // Wait 30 seconds after last event
    )
);
```

### Per-Key Debounce

[](#per-key-debounce)

```
// Separate debounce window for each user
$function = new InngestFunction(
    id: 'sync-user-data',
    handler: function ($ctx) {
        $user_id = $ctx->getEvent()->getData()['user_id'];
        // Sync data once updates stop for this user
        return syncUserData($user_id);
    },
    triggers: [new TriggerEvent('user/updated')],
    debounce: new Debounce(
        period: '5m',
        key: 'event.data.user_id' // Each user has own debounce
    )
);
```

### With Timeout

[](#with-timeout)

```
// Process webhooks, but force execution after maximum wait
$function = new InngestFunction(
    id: 'process-webhook',
    handler: function ($ctx) {
        $data = $ctx->getEvent()->getData();
        // Process either when events stop OR timeout reached
        return processWebhook($data);
    },
    triggers: [new TriggerEvent('webhook/received')],
    debounce: new Debounce(
        period: '1m',    // Wait 1 minute after last event
        timeout: '10m'   // Force run after 10 minutes max
    )
);
```

### Complex Key Expression

[](#complex-key-expression)

```
// Debounce per customer and region combination
$function = new InngestFunction(
    id: 'aggregate-metrics',
    handler: function ($ctx) {
        $data = $ctx->getEvent()->getData();
        return aggregateMetrics($data['customer_id'], $data['region']);
    },
    triggers: [new TriggerEvent('metrics/collected')],
    debounce: new Debounce(
        period: '2m',
        key: 'event.data.customer_id + "-" + event.data.region'
    )
);
```

### Debounce Options

[](#debounce-options)

- **period** (required): Time to wait after last event
    - Format: `` where unit is `s`, `m`, `h`, or `d`
    - Range: `1s` to `7d` (168 hours)
    - Examples: `30s`, `5m`, `2h`, `7d`
- **key** (optional): CEL expression to group debounce windows
    - Each unique key value gets its own debounce period
    - Examples: `event.data.user_id`, `event.data.region`
- **timeout** (optional): Maximum wait time before forcing execution
    - Same format and range as period
    - Ensures function eventually runs even if events keep arriving

**How it works:**

1. First event starts the debounce period
2. Each new matching event resets the period timer
3. Function runs when period expires with no new events
4. If timeout is set, function runs after timeout regardless of new events

**Use cases:**

- **User input**: Wait for user to stop typing before processing
- **Webhook processing**: Batch rapid webhook updates into single run
- **Data synchronization**: Use latest data after updates settle
- **Rate limiting**: Prevent overwhelming downstream services

**Heads-up:**

- Cannot combine debounce with batching
- Function receives only the last event, not all events
- Use rate limiting if you need the first event instead of last

See [examples/debounce.php](examples/debounce.php) for more examples and [Inngest Debounce Documentation](https://www.inngest.com/docs/guides/debounce) for details.

Singleton
---------

[](#singleton)

Ensure only a single run of a function (or per unique key) is executing at a time. Prevents duplicate work, race conditions, and ensures sequential processing of events.

### Basic Singleton - Skip Mode

[](#basic-singleton---skip-mode)

```
use DealNews\Inngest\Function\Singleton;

$function = new InngestFunction(
    id: 'data-sync',
    handler: function ($ctx) {
        // Sync data with third-party API
        // Only one sync can run at a time
        return syncDataWithAPI();
    },
    triggers: [new TriggerEvent('sync/start')],
    singleton: new Singleton(
        mode: 'skip' // Skip new runs if one is executing
    )
);
```

### Per-User Singleton

[](#per-user-singleton)

```
// Each user has their own singleton rule
$function = new InngestFunction(
    id: 'process-user-data',
    handler: function ($ctx) {
        $user_id = $ctx->getEvent()->getData()['user_id'];
        // Process user data (only one run per user at a time)
        return processUserData($user_id);
    },
    triggers: [new TriggerEvent('user/data.updated')],
    singleton: new Singleton(
        mode: 'skip',
        key: 'event.data.user_id' // Separate singleton per user
    )
);
```

### Cancel Mode

[](#cancel-mode)

```
// Always process the latest event
$function = new InngestFunction(
    id: 'sync-latest-profile',
    handler: function ($ctx) {
        $user_id = $ctx->getEvent()->getData()['user_id'];
        // Cancel old sync and start new one with latest data
        return syncUserProfile($user_id);
    },
    triggers: [new TriggerEvent('profile/updated')],
    singleton: new Singleton(
        mode: 'cancel', // Cancel existing run, start new one
        key: 'event.data.user_id'
    )
);
```

### Complex Key Expression

[](#complex-key-expression-1)

```
// Singleton per customer and region combination
$function = new InngestFunction(
    id: 'generate-report',
    handler: function ($ctx) {
        $data = $ctx->getEvent()->getData();
        return generateReport($data['customer_id'], $data['region']);
    },
    triggers: [new TriggerEvent('report/generate')],
    singleton: new Singleton(
        mode: 'skip',
        key: 'event.data.customer_id + "-" + event.data.region'
    )
);
```

### Singleton Options

[](#singleton-options)

- **mode** (required): Behavior when new run arrives
    - `"skip"`: Skip new runs if another is already executing
    - `"cancel"`: Cancel existing run and start the new one
- **key** (optional): CEL expression to group singleton behavior
    - Each unique key value gets its own singleton rule
    - Examples: `event.data.user_id`, `event.data.tenant_id`

### How It Works

[](#how-it-works)

**Skip Mode:**

1. First event starts the function run
2. While running, new matching events are skipped/discarded
3. Function completes with first event's data
4. Next event can then start a new run

**Cancel Mode:**

1. First event starts the function run
2. New matching event cancels the in-progress run
3. New run starts immediately with latest event
4. Rapid events may cause some to be skipped (debounce-like)

### When to Use Each Mode

[](#when-to-use-each-mode)

**Use Skip Mode when:**

- Preventing duplicate work (only need to process once)
- Protecting expensive operations (AI, heavy compute)
- Sequential processing required (database migrations)
- Resource limits (third-party API rate limits)

**Use Cancel Mode when:**

- Latest data matters most (user profile updates)
- Older data becomes stale (real-time dashboards)
- Want to process most recent event (search queries)

### Use Cases

[](#use-cases)

- **Data synchronization**: Third-party API syncs (skip mode)
- **AI processing**: Expensive computations (skip mode)
- **Profile updates**: Always use latest data (cancel mode)
- **Report generation**: One report at a time per customer (skip + key)
- **Database migrations**: Sequential execution required (skip mode)

### Compatibility

[](#compatibility)

**Works with:**

- ✅ Debounce
- ✅ Priority
- ✅ Rate limiting
- ✅ Throttling

**Does not work with:**

- ❌ Batching (singleton incompatible)
- ⚠️ Concurrency (singleton implies concurrency=1)

**Heads-up:**

- Failed functions still skip new runs during retry
- Cancel mode with rapid events may skip some (not all are cancelled)
- Singleton ensures "at most one" run, not "exactly one"

See [examples/singleton.php](examples/singleton.php) for more examples and [Inngest Singleton Documentation](https://www.inngest.com/docs/guides/singleton) for details.

Rate Limiting
-------------

[](#rate-limiting)

Set a hard limit on how many function runs can start within a time period. Events exceeding the limit are **skipped** (not queued). Uses the Generic Cell Rate Algorithm (GCRA) for smooth distribution.

### Basic Rate Limit

[](#basic-rate-limit)

```
use DealNews\Inngest\Function\RateLimit;

$function = new InngestFunction(
    id: 'api-request',
    handler: function ($ctx) {
        // Call external API
        // Maximum 100 calls per hour
        return callExternalAPI($ctx->getEvent()->getData());
    },
    triggers: [new TriggerEvent('api/request')],
    rate_limit: new RateLimit(
        limit: 100,
        period: '1h'
    )
);
```

### Per-User Rate Limiting

[](#per-user-rate-limiting)

```
// Each user has their own rate limit
$function = new InngestFunction(
    id: 'user-api-request',
    handler: function ($ctx) {
        $user_id = $ctx->getEvent()->getData()['user_id'];
        // Limit: 10 requests per 30 minutes per user
        return processUserRequest($user_id);
    },
    triggers: [new TriggerEvent('user/api.request')],
    rate_limit: new RateLimit(
        limit: 10,
        period: '30m',
        key: 'event.data.user_id'
    )
);
```

### Per-Customer and Region

[](#per-customer-and-region)

```
// Complex key expression
$function = new InngestFunction(
    id: 'process-webhook',
    handler: function ($ctx) {
        $data = $ctx->getEvent()->getData();
        return processWebhook($data);
    },
    triggers: [new TriggerEvent('webhook/received')],
    rate_limit: new RateLimit(
        limit: 50,
        period: '1h',
        key: 'event.data.customer_id + "-" + event.data.region'
    )
);
```

### Rate Limit Options

[](#rate-limit-options)

- **limit** (required): Maximum runs per period
    - Must be &gt;= 1
- **period** (required): Time window
    - Format: `` where unit is `s`, `m`, or `h`
    - Range: `1s` to `24h` (does NOT support days)
    - Examples: `30s`, `10m`, `2h`, `24h`
- **key** (optional): CEL expression to group rate limits
    - Each unique key value has its own independent limit
    - Examples: `event.data.user_id`, `event.data.api_key`

### How It Works

[](#how-it-works-1)

1. Uses Generic Cell Rate Algorithm (GCRA) for smooth distribution
2. Events exceeding limit are **skipped/discarded** (lossy behavior)
3. Period begins when first matching event is received
4. Each unique key value has its own rate limit tracker

### Use Cases

[](#use-cases-1)

- **External API limits**: Enforce third-party API rate limits
- **Resource protection**: Prevent overwhelming downstream services
- **Cost control**: Limit expensive operations (AI, compute)
- **Abuse prevention**: Per-user/API key rate limiting

### Compatibility

[](#compatibility-1)

**Works with:**

- ✅ Concurrency, priority, debounce, singleton, throttling

**Does not work with:**

- ❌ Batching (not yet implemented)

**Heads-up:**

- Events exceeding limit are permanently lost (use throttling for lossless queueing)
- Rate limit is enforced server-side by Inngest
- Maximum period is 24 hours (for longer periods, use throttling with up to 7 days)

See [examples/rate-limit.php](examples/rate-limit.php) for more examples and [Inngest Rate Limiting Documentation](https://www.inngest.com/docs/guides/rate-limiting) for details.

Throttling
----------

[](#throttling)

Limit how many function runs can start within a time period, with excess runs **enqueued for future execution** (FIFO). Unlike rate limiting which skips events, throttling ensures no data loss.

### Basic Throttle

[](#basic-throttle)

```
use DealNews\Inngest\Function\Throttle;

$function = new InngestFunction(
    id: 'process-job',
    handler: function ($ctx) {
        // Process job at controlled rate
        // Maximum 10 jobs per hour (excess are queued)
        return processJob($ctx->getEvent()->getData());
    },
    triggers: [new TriggerEvent('job/created')],
    throttle: new Throttle(
        limit: 10,
        period: '1h'
    )
);
```

### With Burst Support

[](#with-burst-support)

```
// Allow bursts above base limit
$function = new InngestFunction(
    id: 'api-sync',
    handler: function ($ctx) {
        // Base: 100/hour, can burst up to 110/hour
        return syncWithAPI($ctx->getEvent()->getData());
    },
    triggers: [new TriggerEvent('sync/request')],
    throttle: new Throttle(
        limit: 100,
        period: '1h',
        burst: 10  // Allow 10 extra runs during spikes
    )
);
```

### Per-User Throttling

[](#per-user-throttling)

```
// Each user has their own throttle
$function = new InngestFunction(
    id: 'user-report',
    handler: function ($ctx) {
        $user_id = $ctx->getEvent()->getData()['user_id'];
        // Limit: 5 reports per 30 minutes per user
        return generateReport($user_id);
    },
    triggers: [new TriggerEvent('report/generate')],
    throttle: new Throttle(
        limit: 5,
        period: '30m',
        key: 'event.data.user_id'
    )
);
```

### Complex Key Expression

[](#complex-key-expression-2)

```
// Throttle per customer and region
$function = new InngestFunction(
    id: 'aggregate-data',
    handler: function ($ctx) {
        $data = $ctx->getEvent()->getData();
        return aggregateData($data);
    },
    triggers: [new TriggerEvent('data/received')],
    throttle: new Throttle(
        limit: 100,
        period: '1h',
        burst: 20,
        key: 'event.data.customer_id + "-" + event.data.region'
    )
);
```

### Throttle Options

[](#throttle-options)

- **limit** (required): Base number of runs per period
    - Must be &gt;= 1
- **period** (required): Time window
    - Format: `` where unit is `s`, `m`, `h`, or `d`
    - Range: `1s` to `7d` (supports days unlike rate limiting)
    - Examples: `30s`, `10m`, `2h`, `7d`
- **burst** (optional): Extra runs allowed during spikes
    - Must be &gt;= 0 (default: 0, no bursting)
    - Total capacity: limit + burst
    - Example: limit=10, burst=5 allows 15 runs per period
- **key** (optional): CEL expression to group throttles
    - Each unique key value has its own throttle
    - Examples: `event.data.user_id`, `event.data.tenant_id`

### How It Works

[](#how-it-works-2)

1. Uses Generic Cell Rate Algorithm (GCRA)
2. Breaks period into windows based on limit
3. Events exceeding limit are **enqueued (FIFO)** for future execution
4. Burst parameter allows temporary spikes
5. Each unique key value has its own throttle tracker

### FIFO Behavior

[](#fifo-behavior)

When the limit is reached:

1. Excess events are added to a queue (First In, First Out)
2. As capacity becomes available, queued events execute in order
3. No events are lost (unlike rate limiting)

### Burst Parameter

[](#burst-parameter)

The burst parameter handles traffic spikes:

- **Without burst** (burst=0): Admits exactly `limit` runs per period
- **With burst** (burst&gt;0): Admits up to `limit + burst` runs per period
- Example: `limit: 100, burst: 10` allows 110 runs per hour during spikes
- Useful for: API burst allowances, handling temporary traffic increases

### Use Cases

[](#use-cases-2)

- **API rate limit compliance**: Match external API limits with burst allowance
- **Smooth traffic spikes**: Evenly distribute execution over time
- **Background job processing**: Control processing rate without data loss
- **Resource management**: Prevent overwhelming services while queuing excess
- **Cost control**: Limit expensive operations but process all eventually

### Throttle vs Rate Limit

[](#throttle-vs-rate-limit)

FeatureThrottleRate Limit**Excess events**Enqueued (FIFO)Skipped**Data loss**No ✅Yes (lossy) ❌**Max period**7 days24 hours**Burst support**Yes ✅No**Use case**Process all eventsHard limits### Compatibility

[](#compatibility-2)

**Works with:**

- ✅ Concurrency, priority, debounce, singleton, rate limiting

**Does not work with:**

- ❌ Batching (not yet implemented)

**Heads-up:**

- All events are eventually processed (FIFO queue)
- Burst capacity refreshes each period
- Throttle is enforced server-side by Inngest
- Supports longer periods (7 days) vs rate limiting (24 hours max)

See [examples/throttle.php](examples/throttle.php) for more examples and [Inngest Throttling Documentation](https://www.inngest.com/docs/guides/throttling) for details.

Development
-----------

[](#development)

The SDK follows PSR standards and uses:

- snake\_case for variables and properties
- camelCase for method names
- Protected visibility by default

Testing
-------

[](#testing)

```
composer test
```

Resources
---------

[](#resources)

- [Inngest Documentation](https://www.inngest.com/docs)
- [SDK Specification](https://github.com/inngest/inngest/blob/main/docs/SDK_SPEC.md)
- [Support](https://www.inngest.com/support)

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance88

Actively maintained with recent releases

Popularity25

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~8 days

Recently: every ~25 days

Total

15

Last Release

76d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/49531?v=4)[Brian Moon](/maintainers/brianlmoon)[@brianlmoon](https://github.com/brianlmoon)

![](https://www.gravatar.com/avatar/dbf067b8b1b679cc96fb0e13483a6be5b90cb35dfbb85d471cee151b9fa87417?d=identicon)[dealnews](/maintainers/dealnews)

---

Top Contributors

[![brianlmoon](https://avatars.githubusercontent.com/u/49531?v=4)](https://github.com/brianlmoon "brianlmoon (30 commits)")

---

Tags

event-drivenqueueworkflowserverlessbackground-jobsinngest

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/dealnews-inngest-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/dealnews-inngest-php-sdk/health.svg)](https://phpackages.com/packages/dealnews-inngest-php-sdk)
```

###  Alternatives

[php-amqplib/rabbitmq-bundle

Integrates php-amqplib with Symfony &amp; RabbitMq. Formerly emag-tech-labs/rabbitmq-bundle, oldsound/rabbitmq-bundle.

1.3k20.1M65](/packages/php-amqplib-rabbitmq-bundle)[enqueue/enqueue

Message Queue Library

19820.0M56](/packages/enqueue-enqueue)[swarrot/swarrot

A simple lib to consume RabbitMQ queues

3654.4M8](/packages/swarrot-swarrot)[koco/messenger-kafka

Symfony Messenger Kafka Transport

931.1M1](/packages/koco-messenger-kafka)[croustibat/filament-jobs-monitor

Background Jobs monitoring like Horizon for all drivers for FilamentPHP

254255.2k6](/packages/croustibat-filament-jobs-monitor)[nuwber/rabbitevents

The Nuwber RabbitEvents package

120515.8k3](/packages/nuwber-rabbitevents)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
