$npx -y skills add inngest/inngest-skills --skill inngest-flow-controlUse when handling external API rate limits (e.g., OpenAI 429s, HubSpot or Stripe rate limits), preventing duplicate work from rapid event bursts (debouncing user actions), spreading load over time, ensuring per-tenant fairness, processing events in batches, limiting concurrent ru
| 1 | # Inngest Flow Control |
| 2 | |
| 3 | Master Inngest flow control mechanisms to manage resources, prevent overloading systems, and ensure application reliability. This skill covers all flow control options with prescriptive guidance on when and how to use each. |
| 4 | |
| 5 | > **These skills are focused on TypeScript.** For Python or Go, refer to the [Inngest documentation](https://www.inngest.com/llms.txt) for language-specific guidance. Core concepts apply across all languages. |
| 6 | |
| 7 | ## Quick Decision Guide |
| 8 | |
| 9 | - **"Limit how many run at once"** → Concurrency |
| 10 | - **"Spread runs over time"** → Throttling |
| 11 | - **"Block after N runs in a period"** → Rate Limiting |
| 12 | - **"Wait for activity to stop, then run once"** → Debounce |
| 13 | - **"Only one run at a time for this key"** → Singleton |
| 14 | - **"Process events in groups"** → Batching |
| 15 | - **"Some runs are more important"** → Priority |
| 16 | |
| 17 | ## Concurrency |
| 18 | |
| 19 | **When to use:** Limit the number of executing steps (not function runs) to manage computing resources and prevent system overwhelm. |
| 20 | |
| 21 | **Key insight:** Concurrency limits active code execution, not function runs. A function waiting on `step.sleep()` or `step.waitForEvent()` doesn't count against the limit. |
| 22 | |
| 23 | ### Basic Concurrency |
| 24 | |
| 25 | ```typescript |
| 26 | inngest.createFunction( |
| 27 | { |
| 28 | id: "process-images", |
| 29 | concurrency: 5, |
| 30 | triggers: [{ event: "media/image.uploaded" }] |
| 31 | }, |
| 32 | async ({ event, step }) => { |
| 33 | // Only 5 steps can execute simultaneously |
| 34 | await step.run("resize", () => resizeImage(event.data.imageUrl)); |
| 35 | } |
| 36 | ); |
| 37 | ``` |
| 38 | |
| 39 | ### Concurrency with Keys (Multi-tenant) |
| 40 | |
| 41 | Use `key` parameter to apply limit per unique value of the key. |
| 42 | |
| 43 | ```typescript |
| 44 | inngest.createFunction( |
| 45 | { |
| 46 | id: "user-sync", |
| 47 | concurrency: [ |
| 48 | { |
| 49 | key: "event.data.user_id", |
| 50 | limit: 1 |
| 51 | } |
| 52 | ], |
| 53 | triggers: [{ event: "user/profile.updated" }] |
| 54 | }, |
| 55 | async ({ event, step }) => { |
| 56 | // Only 1 step per user can execute at once |
| 57 | // Prevents race conditions in user-specific operations |
| 58 | } |
| 59 | ); |
| 60 | ``` |
| 61 | |
| 62 | ### Account-level Shared Limits |
| 63 | |
| 64 | ```typescript |
| 65 | inngest.createFunction( |
| 66 | { |
| 67 | id: "ai-summary", |
| 68 | concurrency: [ |
| 69 | { |
| 70 | scope: "account", |
| 71 | key: `"openai"`, |
| 72 | limit: 60 |
| 73 | } |
| 74 | ], |
| 75 | triggers: [{ event: "ai/summary.requested" }] |
| 76 | }, |
| 77 | async ({ event, step }) => { |
| 78 | // Share 60 concurrent OpenAI calls across all functions |
| 79 | } |
| 80 | ); |
| 81 | ``` |
| 82 | |
| 83 | **When to use each:** |
| 84 | |
| 85 | - Basic: Protect databases or limit general capacity |
| 86 | - Keyed: Multi-tenant fairness, prevent "noisy neighbor" issues |
| 87 | - Account-level: Share quotas across multiple functions (API limits) |
| 88 | |
| 89 | ## Throttling |
| 90 | |
| 91 | **When to use:** Control the rate of function starts over time to work around API rate limits or smooth traffic spikes. |
| 92 | |
| 93 | **Key difference from concurrency:** Throttling limits function run starts; concurrency limits step execution. |
| 94 | |
| 95 | ```typescript |
| 96 | inngest.createFunction( |
| 97 | { |
| 98 | id: "sync-crm-data", |
| 99 | throttle: { |
| 100 | limit: 10, // 10 function starts |
| 101 | period: "60s", // per minute |
| 102 | burst: 5, // plus 5 immediate bursts |
| 103 | key: "event.data.customer_id" // per customer |
| 104 | }, |
| 105 | triggers: [{ event: "crm/contact.updated" }] |
| 106 | }, |
| 107 | async ({ event, step }) => { |
| 108 | // Respects CRM API rate limits: 10 calls/min per customer |
| 109 | await step.run("sync", () => crmApi.updateContact(event.data)); |
| 110 | } |
| 111 | ); |
| 112 | ``` |
| 113 | |
| 114 | **Configuration:** |
| 115 | |
| 116 | - `limit`: Functions that can start per period |
| 117 | - `period`: Time window (1s to 7d) |
| 118 | - `burst`: Extra immediate starts allowed |
| 119 | - `key`: Apply limits per unique key value |
| 120 | |
| 121 | ## Rate Limiting |
| 122 | |
| 123 | **When to use:** Hard limit to prevent abuse or skip excessive duplicate events. |
| 124 | |
| 125 | **Key difference from throttling:** Rate limiting discards events; throttling delays them. |
| 126 | |
| 127 | ```typescript |
| 128 | inngest.createFunction( |
| 129 | { |
| 130 | id: "webhook-processor", |
| 131 | rateLimit: { |
| 132 | limit: 1, |
| 133 | period: "4h", |
| 134 | key: "event.data.webhook_id" |
| 135 | }, |
| 136 | triggers: [{ event: "webhook/data.received" }] |
| 137 | }, |
| 138 | async ({ event, step }) => { |
| 139 | // Process each webhook only once per 4 hours |
| 140 | // Prevents duplicate webhook spam |
| 141 | } |
| 142 | ); |
| 143 | ``` |
| 144 | |
| 145 | **Use cases:** |
| 146 | |
| 147 | - Prevent webhook duplicates |
| 148 | - Limit expensive operations per user |
| 149 | - Protection against abuse |
| 150 | |
| 151 | ## Debounce |
| 152 | |
| 153 | **When to use:** Wait for a series of events to stop arriving before processing the latest one. |
| 154 | |
| 155 | ```typescript |
| 156 | inngest.createFunction( |
| 157 | { |
| 158 | id: "save-document", |
| 159 | debounce: { |
| 160 | period: "5m", // Wait 5min after last edit |
| 161 | key: "event.data.document_id", |
| 162 | timeout: "30m" // Force save after 30min max |
| 163 | }, |
| 164 | trigger |