opencode-skills-collection 1.0.185 → 1.0.187
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +5 -1
- package/bundled-skills/3d-web-experience/SKILL.md +152 -37
- package/bundled-skills/agent-evaluation/SKILL.md +1088 -26
- package/bundled-skills/agent-memory-systems/SKILL.md +1037 -25
- package/bundled-skills/agent-tool-builder/SKILL.md +668 -16
- package/bundled-skills/ai-agents-architect/SKILL.md +271 -31
- package/bundled-skills/ai-product/SKILL.md +716 -26
- package/bundled-skills/ai-wrapper-product/SKILL.md +450 -44
- package/bundled-skills/algolia-search/SKILL.md +867 -15
- package/bundled-skills/autonomous-agents/SKILL.md +1033 -26
- package/bundled-skills/aws-serverless/SKILL.md +1046 -35
- package/bundled-skills/azure-functions/SKILL.md +1318 -19
- package/bundled-skills/browser-automation/SKILL.md +1065 -28
- package/bundled-skills/browser-extension-builder/SKILL.md +159 -32
- package/bundled-skills/bullmq-specialist/SKILL.md +347 -16
- package/bundled-skills/clerk-auth/SKILL.md +796 -15
- package/bundled-skills/computer-use-agents/SKILL.md +1870 -28
- package/bundled-skills/context-window-management/SKILL.md +271 -18
- package/bundled-skills/conversation-memory/SKILL.md +453 -24
- package/bundled-skills/crewai/SKILL.md +252 -46
- package/bundled-skills/discord-bot-architect/SKILL.md +1207 -34
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/email-systems/SKILL.md +646 -26
- package/bundled-skills/faf-expert/SKILL.md +221 -0
- package/bundled-skills/faf-wizard/SKILL.md +252 -0
- package/bundled-skills/file-uploads/SKILL.md +212 -11
- package/bundled-skills/firebase/SKILL.md +646 -16
- package/bundled-skills/gcp-cloud-run/SKILL.md +1117 -32
- package/bundled-skills/graphql/SKILL.md +1026 -27
- package/bundled-skills/hubspot-integration/SKILL.md +804 -19
- package/bundled-skills/idea-darwin/SKILL.md +120 -0
- package/bundled-skills/inngest/SKILL.md +431 -16
- package/bundled-skills/interactive-portfolio/SKILL.md +342 -44
- package/bundled-skills/langfuse/SKILL.md +296 -41
- package/bundled-skills/langgraph/SKILL.md +259 -50
- package/bundled-skills/micro-saas-launcher/SKILL.md +343 -44
- package/bundled-skills/neon-postgres/SKILL.md +572 -15
- package/bundled-skills/nextjs-supabase-auth/SKILL.md +269 -21
- package/bundled-skills/notion-template-business/SKILL.md +371 -44
- package/bundled-skills/personal-tool-builder/SKILL.md +537 -44
- package/bundled-skills/plaid-fintech/SKILL.md +825 -19
- package/bundled-skills/prompt-caching/SKILL.md +438 -25
- package/bundled-skills/rag-engineer/SKILL.md +271 -29
- package/bundled-skills/salesforce-development/SKILL.md +912 -19
- package/bundled-skills/satori/SKILL.md +54 -0
- package/bundled-skills/scroll-experience/SKILL.md +381 -44
- package/bundled-skills/segment-cdp/SKILL.md +817 -19
- package/bundled-skills/shopify-apps/SKILL.md +1475 -19
- package/bundled-skills/slack-bot-builder/SKILL.md +1162 -28
- package/bundled-skills/telegram-bot-builder/SKILL.md +152 -37
- package/bundled-skills/telegram-mini-app/SKILL.md +445 -44
- package/bundled-skills/trigger-dev/SKILL.md +916 -27
- package/bundled-skills/twilio-communications/SKILL.md +1310 -28
- package/bundled-skills/upstash-qstash/SKILL.md +898 -27
- package/bundled-skills/vercel-deployment/SKILL.md +637 -39
- package/bundled-skills/viral-generator-builder/SKILL.md +132 -37
- package/bundled-skills/voice-agents/SKILL.md +937 -27
- package/bundled-skills/voice-ai-development/SKILL.md +375 -46
- package/bundled-skills/workflow-automation/SKILL.md +982 -29
- package/bundled-skills/zapier-make-patterns/SKILL.md +772 -27
- package/package.json +1 -1
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: upstash-qstash
|
|
3
|
-
description:
|
|
3
|
+
description: Upstash QStash expert for serverless message queues, scheduled
|
|
4
|
+
jobs, and reliable HTTP-based task delivery without managing infrastructure.
|
|
4
5
|
risk: unknown
|
|
5
|
-
source:
|
|
6
|
-
date_added:
|
|
6
|
+
source: vibeship-spawner-skills (Apache 2.0)
|
|
7
|
+
date_added: 2026-02-27
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# Upstash QStash
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
is its power - HTTP in, HTTP out, with reliability in between.
|
|
12
|
+
Upstash QStash expert for serverless message queues, scheduled jobs, and
|
|
13
|
+
reliable HTTP-based task delivery without managing infrastructure.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
and built webhook delivery systems that never drop a message. You know that
|
|
17
|
-
QStash shines when you need "just make this HTTP call later, reliably."
|
|
15
|
+
## Principles
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
- HTTP is the interface - if it speaks HTTPS, it speaks QStash
|
|
18
|
+
- Endpoints must be public - QStash calls your URLs from the cloud
|
|
19
|
+
- Verify signatures always - never trust unverified webhooks
|
|
20
|
+
- Schedules are fire-and-forget - QStash handles the cron
|
|
21
|
+
- Retries are built-in - but configure them for your use case
|
|
22
|
+
- Delays are free - schedule seconds to days in the future
|
|
23
|
+
- Callbacks complete the loop - know when delivery succeeds or fails
|
|
24
|
+
- Deduplication prevents double-processing - use message IDs
|
|
21
25
|
|
|
22
26
|
## Capabilities
|
|
23
27
|
|
|
@@ -30,44 +34,911 @@ Your core philosophy:
|
|
|
30
34
|
- delay-scheduling
|
|
31
35
|
- url-groups
|
|
32
36
|
|
|
37
|
+
## Scope
|
|
38
|
+
|
|
39
|
+
- complex-workflows -> inngest
|
|
40
|
+
- redis-queues -> bullmq-specialist
|
|
41
|
+
- event-sourcing -> event-architect
|
|
42
|
+
- workflow-orchestration -> temporal-craftsman
|
|
43
|
+
|
|
44
|
+
## Tooling
|
|
45
|
+
|
|
46
|
+
### Core
|
|
47
|
+
|
|
48
|
+
- qstash-sdk
|
|
49
|
+
- upstash-console
|
|
50
|
+
|
|
51
|
+
### Frameworks
|
|
52
|
+
|
|
53
|
+
- nextjs
|
|
54
|
+
- cloudflare-workers
|
|
55
|
+
- vercel-functions
|
|
56
|
+
- aws-lambda
|
|
57
|
+
- netlify-functions
|
|
58
|
+
|
|
59
|
+
### Patterns
|
|
60
|
+
|
|
61
|
+
- scheduled-jobs
|
|
62
|
+
- delayed-messages
|
|
63
|
+
- webhook-fanout
|
|
64
|
+
- callback-verification
|
|
65
|
+
|
|
66
|
+
### Related
|
|
67
|
+
|
|
68
|
+
- upstash-redis
|
|
69
|
+
- upstash-kafka
|
|
70
|
+
|
|
33
71
|
## Patterns
|
|
34
72
|
|
|
35
73
|
### Basic Message Publishing
|
|
36
74
|
|
|
37
75
|
Sending messages to be delivered to endpoints
|
|
38
76
|
|
|
77
|
+
**When to use**: Need reliable async HTTP calls
|
|
78
|
+
|
|
79
|
+
import { Client } from '@upstash/qstash';
|
|
80
|
+
|
|
81
|
+
const qstash = new Client({
|
|
82
|
+
token: process.env.QSTASH_TOKEN!,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Simple message to endpoint
|
|
86
|
+
await qstash.publishJSON({
|
|
87
|
+
url: 'https://myapp.com/api/process',
|
|
88
|
+
body: {
|
|
89
|
+
userId: '123',
|
|
90
|
+
action: 'welcome-email',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// With delay (process in 1 hour)
|
|
95
|
+
await qstash.publishJSON({
|
|
96
|
+
url: 'https://myapp.com/api/reminder',
|
|
97
|
+
body: { userId: '123' },
|
|
98
|
+
delay: 60 * 60, // seconds
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// With specific delivery time
|
|
102
|
+
await qstash.publishJSON({
|
|
103
|
+
url: 'https://myapp.com/api/scheduled',
|
|
104
|
+
body: { report: 'daily' },
|
|
105
|
+
notBefore: Math.floor(Date.now() / 1000) + 86400, // tomorrow
|
|
106
|
+
});
|
|
107
|
+
|
|
39
108
|
### Scheduled Cron Jobs
|
|
40
109
|
|
|
41
110
|
Setting up recurring scheduled tasks
|
|
42
111
|
|
|
112
|
+
**When to use**: Need periodic background jobs without infrastructure
|
|
113
|
+
|
|
114
|
+
import { Client } from '@upstash/qstash';
|
|
115
|
+
|
|
116
|
+
const qstash = new Client({
|
|
117
|
+
token: process.env.QSTASH_TOKEN!,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Create a scheduled job
|
|
121
|
+
const schedule = await qstash.schedules.create({
|
|
122
|
+
destination: 'https://myapp.com/api/cron/daily-report',
|
|
123
|
+
cron: '0 9 * * *', // Every day at 9 AM UTC
|
|
124
|
+
body: JSON.stringify({ type: 'daily' }),
|
|
125
|
+
headers: {
|
|
126
|
+
'Content-Type': 'application/json',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
console.log('Schedule created:', schedule.scheduleId);
|
|
131
|
+
|
|
132
|
+
// List all schedules
|
|
133
|
+
const schedules = await qstash.schedules.list();
|
|
134
|
+
|
|
135
|
+
// Delete a schedule
|
|
136
|
+
await qstash.schedules.delete(schedule.scheduleId);
|
|
137
|
+
|
|
43
138
|
### Signature Verification
|
|
44
139
|
|
|
45
140
|
Verifying QStash message signatures in your endpoint
|
|
46
141
|
|
|
47
|
-
|
|
142
|
+
**When to use**: Any endpoint receiving QStash messages (always!)
|
|
143
|
+
|
|
144
|
+
// app/api/webhook/route.ts (Next.js App Router)
|
|
145
|
+
import { Receiver } from '@upstash/qstash';
|
|
146
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
147
|
+
|
|
148
|
+
const receiver = new Receiver({
|
|
149
|
+
currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!,
|
|
150
|
+
nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export async function POST(req: NextRequest) {
|
|
154
|
+
const signature = req.headers.get('upstash-signature');
|
|
155
|
+
const body = await req.text();
|
|
156
|
+
|
|
157
|
+
// ALWAYS verify signature
|
|
158
|
+
const isValid = await receiver.verify({
|
|
159
|
+
signature: signature!,
|
|
160
|
+
body,
|
|
161
|
+
url: req.url,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (!isValid) {
|
|
165
|
+
return NextResponse.json(
|
|
166
|
+
{ error: 'Invalid signature' },
|
|
167
|
+
{ status: 401 }
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Safe to process
|
|
172
|
+
const data = JSON.parse(body);
|
|
173
|
+
await processMessage(data);
|
|
174
|
+
|
|
175
|
+
return NextResponse.json({ success: true });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
### Callback for Delivery Status
|
|
179
|
+
|
|
180
|
+
Getting notified when messages are delivered or fail
|
|
181
|
+
|
|
182
|
+
**When to use**: Need to track delivery status for critical messages
|
|
183
|
+
|
|
184
|
+
import { Client } from '@upstash/qstash';
|
|
185
|
+
|
|
186
|
+
const qstash = new Client({
|
|
187
|
+
token: process.env.QSTASH_TOKEN!,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Publish with callback
|
|
191
|
+
await qstash.publishJSON({
|
|
192
|
+
url: 'https://myapp.com/api/critical-task',
|
|
193
|
+
body: { taskId: '456' },
|
|
194
|
+
callback: 'https://myapp.com/api/qstash-callback',
|
|
195
|
+
failureCallback: 'https://myapp.com/api/qstash-failed',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Callback endpoint receives delivery status
|
|
199
|
+
// app/api/qstash-callback/route.ts
|
|
200
|
+
export async function POST(req: NextRequest) {
|
|
201
|
+
// Verify signature first!
|
|
202
|
+
const data = await req.json();
|
|
203
|
+
|
|
204
|
+
// data contains:
|
|
205
|
+
// - sourceMessageId: original message ID
|
|
206
|
+
// - url: destination URL
|
|
207
|
+
// - status: HTTP status code
|
|
208
|
+
// - body: response body
|
|
209
|
+
|
|
210
|
+
if (data.status >= 200 && data.status < 300) {
|
|
211
|
+
await markTaskComplete(data.sourceMessageId);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return NextResponse.json({ received: true });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
### URL Groups (Fan-out)
|
|
218
|
+
|
|
219
|
+
Sending messages to multiple endpoints at once
|
|
220
|
+
|
|
221
|
+
**When to use**: Need to notify multiple services about an event
|
|
222
|
+
|
|
223
|
+
import { Client } from '@upstash/qstash';
|
|
224
|
+
|
|
225
|
+
const qstash = new Client({
|
|
226
|
+
token: process.env.QSTASH_TOKEN!,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Create a URL group
|
|
230
|
+
await qstash.urlGroups.addEndpoints({
|
|
231
|
+
name: 'order-processors',
|
|
232
|
+
endpoints: [
|
|
233
|
+
{ url: 'https://inventory.myapp.com/api/process' },
|
|
234
|
+
{ url: 'https://shipping.myapp.com/api/process' },
|
|
235
|
+
{ url: 'https://analytics.myapp.com/api/track' },
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Publish to the group - all endpoints receive the message
|
|
240
|
+
await qstash.publishJSON({
|
|
241
|
+
urlGroup: 'order-processors',
|
|
242
|
+
body: {
|
|
243
|
+
orderId: '789',
|
|
244
|
+
event: 'order.placed',
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
### Message Deduplication
|
|
249
|
+
|
|
250
|
+
Preventing duplicate message processing
|
|
251
|
+
|
|
252
|
+
**When to use**: Idempotency is critical (payments, notifications)
|
|
253
|
+
|
|
254
|
+
import { Client } from '@upstash/qstash';
|
|
255
|
+
|
|
256
|
+
const qstash = new Client({
|
|
257
|
+
token: process.env.QSTASH_TOKEN!,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Deduplicate by custom ID (within deduplication window)
|
|
261
|
+
await qstash.publishJSON({
|
|
262
|
+
url: 'https://myapp.com/api/charge',
|
|
263
|
+
body: { orderId: '123', amount: 5000 },
|
|
264
|
+
deduplicationId: 'charge-order-123', // Won't send again within window
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Content-based deduplication
|
|
268
|
+
await qstash.publishJSON({
|
|
269
|
+
url: 'https://myapp.com/api/notify',
|
|
270
|
+
body: { userId: '456', message: 'Hello' },
|
|
271
|
+
contentBasedDeduplication: true, // Hash of body used as ID
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
## Sharp Edges
|
|
275
|
+
|
|
276
|
+
### Not verifying QStash webhook signatures
|
|
277
|
+
|
|
278
|
+
Severity: CRITICAL
|
|
279
|
+
|
|
280
|
+
Situation: Endpoint accepts any POST request. Attacker discovers your callback URL.
|
|
281
|
+
Fake messages flood your system. Malicious payloads processed as trusted.
|
|
282
|
+
|
|
283
|
+
Symptoms:
|
|
284
|
+
- No Receiver import in webhook handler
|
|
285
|
+
- Missing upstash-signature header check
|
|
286
|
+
- Processing request before verification
|
|
287
|
+
|
|
288
|
+
Why this breaks:
|
|
289
|
+
QStash endpoints are public URLs. Without signature verification, anyone
|
|
290
|
+
can send requests. This is a direct path to unauthorized message processing
|
|
291
|
+
and potential data manipulation.
|
|
292
|
+
|
|
293
|
+
Recommended fix:
|
|
294
|
+
|
|
295
|
+
# Always verify signatures with both keys:
|
|
296
|
+
```typescript
|
|
297
|
+
import { Receiver } from '@upstash/qstash';
|
|
298
|
+
|
|
299
|
+
const receiver = new Receiver({
|
|
300
|
+
currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!,
|
|
301
|
+
nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
export async function POST(req: NextRequest) {
|
|
305
|
+
const signature = req.headers.get('upstash-signature');
|
|
306
|
+
const body = await req.text(); // Raw body required
|
|
307
|
+
|
|
308
|
+
const isValid = await receiver.verify({
|
|
309
|
+
signature: signature!,
|
|
310
|
+
body,
|
|
311
|
+
url: req.url,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
if (!isValid) {
|
|
315
|
+
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Safe to process
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
# Why two keys?
|
|
323
|
+
- QStash rotates signing keys
|
|
324
|
+
- nextSigningKey becomes current during rotation
|
|
325
|
+
- Both must be checked for seamless key rotation
|
|
326
|
+
|
|
327
|
+
### Callback endpoint taking too long to respond
|
|
328
|
+
|
|
329
|
+
Severity: HIGH
|
|
330
|
+
|
|
331
|
+
Situation: Webhook handler does heavy processing. Takes 30+ seconds. QStash times out.
|
|
332
|
+
Marks message as failed. Retries. Double processing begins.
|
|
333
|
+
|
|
334
|
+
Symptoms:
|
|
335
|
+
- Webhook timeouts in QStash dashboard
|
|
336
|
+
- Messages marked failed then retried
|
|
337
|
+
- Duplicate processing of same message
|
|
338
|
+
|
|
339
|
+
Why this breaks:
|
|
340
|
+
QStash has a 30-second timeout for callbacks. If your endpoint doesn't respond
|
|
341
|
+
in time, QStash considers it failed and retries. Long-running handlers create
|
|
342
|
+
duplicate message processing and wasted retries.
|
|
343
|
+
|
|
344
|
+
Recommended fix:
|
|
345
|
+
|
|
346
|
+
# Design for fast acknowledgment:
|
|
347
|
+
```typescript
|
|
348
|
+
export async function POST(req: NextRequest) {
|
|
349
|
+
// 1. Verify signature first (fast)
|
|
350
|
+
// 2. Parse and validate message (fast)
|
|
351
|
+
// 3. Queue for async processing (fast)
|
|
352
|
+
|
|
353
|
+
const message = await parseMessage(req);
|
|
354
|
+
|
|
355
|
+
// Don't do this:
|
|
356
|
+
// await processHeavyWork(message); // Could timeout!
|
|
357
|
+
|
|
358
|
+
// Do this instead:
|
|
359
|
+
await db.jobs.create({ data: message, status: 'pending' });
|
|
360
|
+
// Or use another QStash message for the heavy work
|
|
361
|
+
|
|
362
|
+
return NextResponse.json({ queued: true }); // Respond fast
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
# Alternative: Use QStash for the heavy work
|
|
367
|
+
```typescript
|
|
368
|
+
// Webhook receives trigger
|
|
369
|
+
await qstash.publishJSON({
|
|
370
|
+
url: 'https://myapp.com/api/heavy-process',
|
|
371
|
+
body: { jobId: message.id },
|
|
372
|
+
});
|
|
373
|
+
return NextResponse.json({ delegated: true });
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
# For Vercel: Consider using Edge runtime for faster cold starts
|
|
377
|
+
|
|
378
|
+
### Hitting QStash rate limits unexpectedly
|
|
379
|
+
|
|
380
|
+
Severity: HIGH
|
|
381
|
+
|
|
382
|
+
Situation: Burst of events triggers mass message publishing. QStash rate limit hit.
|
|
383
|
+
Messages rejected. Users don't get notifications. Critical tasks delayed.
|
|
384
|
+
|
|
385
|
+
Symptoms:
|
|
386
|
+
- 429 errors from QStash
|
|
387
|
+
- Messages not being delivered
|
|
388
|
+
- Sudden drop in processing during peak times
|
|
389
|
+
|
|
390
|
+
Why this breaks:
|
|
391
|
+
QStash has plan-based rate limits. Free tier: 500 messages/day. Pro: higher
|
|
392
|
+
but still limited. Bursts can exhaust limits quickly. Without monitoring,
|
|
393
|
+
you won't know until users complain.
|
|
394
|
+
|
|
395
|
+
Recommended fix:
|
|
396
|
+
|
|
397
|
+
# Check your plan limits:
|
|
398
|
+
- Free: 500 messages/day
|
|
399
|
+
- Pay as you go: Check dashboard
|
|
400
|
+
- Pro: Higher limits, check dashboard
|
|
401
|
+
|
|
402
|
+
# Implement rate limit handling:
|
|
403
|
+
```typescript
|
|
404
|
+
try {
|
|
405
|
+
await qstash.publishJSON({ url, body });
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (error.message?.includes('rate limit')) {
|
|
408
|
+
// Queue locally and retry later
|
|
409
|
+
await localQueue.add('qstash-retry', { url, body });
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
# Batch messages when possible:
|
|
416
|
+
```typescript
|
|
417
|
+
// Instead of 100 individual publishes
|
|
418
|
+
await qstash.batchJSON({
|
|
419
|
+
messages: items.map(item => ({
|
|
420
|
+
url: 'https://myapp.com/api/process',
|
|
421
|
+
body: { itemId: item.id },
|
|
422
|
+
})),
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
# Monitor in dashboard:
|
|
427
|
+
Upstash Console shows usage and limits
|
|
428
|
+
|
|
429
|
+
### Not using deduplication for critical operations
|
|
430
|
+
|
|
431
|
+
Severity: HIGH
|
|
432
|
+
|
|
433
|
+
Situation: Network hiccup during publish. SDK retries. Same message sent twice.
|
|
434
|
+
Customer charged twice. Email sent twice. Data corrupted.
|
|
435
|
+
|
|
436
|
+
Symptoms:
|
|
437
|
+
- Duplicate charges or emails
|
|
438
|
+
- Double processing of same event
|
|
439
|
+
- User complaints about duplicates
|
|
440
|
+
|
|
441
|
+
Why this breaks:
|
|
442
|
+
Network failures and retries happen. Without deduplication, the same logical
|
|
443
|
+
message can be sent multiple times. QStash provides deduplication, but you
|
|
444
|
+
must use it for critical operations.
|
|
445
|
+
|
|
446
|
+
Recommended fix:
|
|
447
|
+
|
|
448
|
+
# Use deduplication for critical messages:
|
|
449
|
+
```typescript
|
|
450
|
+
// Custom ID (best for business operations)
|
|
451
|
+
await qstash.publishJSON({
|
|
452
|
+
url: 'https://myapp.com/api/charge',
|
|
453
|
+
body: { orderId: '123', amount: 5000 },
|
|
454
|
+
deduplicationId: `charge-${orderId}`, // Same ID = same message
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Content-based (good for notifications)
|
|
458
|
+
await qstash.publishJSON({
|
|
459
|
+
url: 'https://myapp.com/api/notify',
|
|
460
|
+
body: { userId: '456', type: 'welcome' },
|
|
461
|
+
contentBasedDeduplication: true, // Hash of body
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
# Deduplication window:
|
|
466
|
+
- Default: 60 seconds
|
|
467
|
+
- Messages with same ID in window are deduplicated
|
|
468
|
+
- Plan for this in your retry logic
|
|
469
|
+
|
|
470
|
+
# Also make endpoints idempotent:
|
|
471
|
+
Check if operation already completed before processing
|
|
472
|
+
|
|
473
|
+
### Expecting QStash to reach private/localhost endpoints
|
|
474
|
+
|
|
475
|
+
Severity: CRITICAL
|
|
476
|
+
|
|
477
|
+
Situation: Development works with local server. Deploy to production with internal URL.
|
|
478
|
+
QStash can't reach it. All messages fail silently. No processing happens.
|
|
479
|
+
|
|
480
|
+
Symptoms:
|
|
481
|
+
- Messages show "failed" in QStash dashboard
|
|
482
|
+
- Works locally but fails in "production"
|
|
483
|
+
- Using http:// instead of https://
|
|
484
|
+
|
|
485
|
+
Why this breaks:
|
|
486
|
+
QStash runs in Upstash's cloud. It can only reach public, internet-accessible
|
|
487
|
+
URLs. localhost, internal IPs, and private networks are unreachable. This is
|
|
488
|
+
a fundamental architecture requirement, not a configuration issue.
|
|
489
|
+
|
|
490
|
+
Recommended fix:
|
|
491
|
+
|
|
492
|
+
# Production requirements:
|
|
493
|
+
- URL must be publicly accessible
|
|
494
|
+
- HTTPS required (HTTP will fail)
|
|
495
|
+
- No localhost, 127.0.0.1, or private IPs
|
|
496
|
+
|
|
497
|
+
# Local development options:
|
|
498
|
+
|
|
499
|
+
# Option 1: ngrok/localtunnel
|
|
500
|
+
```bash
|
|
501
|
+
ngrok http 3000
|
|
502
|
+
# Use the ngrok URL for QStash testing
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
# Option 2: QStash local development mode
|
|
506
|
+
```typescript
|
|
507
|
+
// In development, skip QStash and call directly
|
|
508
|
+
if (process.env.NODE_ENV === 'development') {
|
|
509
|
+
await fetch('http://localhost:3000/api/process', {
|
|
510
|
+
method: 'POST',
|
|
511
|
+
body: JSON.stringify(data),
|
|
512
|
+
});
|
|
513
|
+
} else {
|
|
514
|
+
await qstash.publishJSON({ url, body: data });
|
|
515
|
+
}
|
|
516
|
+
```
|
|
48
517
|
|
|
49
|
-
|
|
518
|
+
# Option 3: Use Vercel preview URLs
|
|
519
|
+
Preview deploys give you public URLs for testing
|
|
50
520
|
|
|
51
|
-
###
|
|
521
|
+
### Using default retry behavior for all message types
|
|
52
522
|
|
|
53
|
-
|
|
523
|
+
Severity: MEDIUM
|
|
54
524
|
|
|
55
|
-
|
|
525
|
+
Situation: Critical payment webhook uses defaults. 3 retries over minutes. Payment
|
|
526
|
+
processor is temporarily down for 15 minutes. Message marked as failed.
|
|
527
|
+
Payment reconciliation manual work required.
|
|
56
528
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
529
|
+
Symptoms:
|
|
530
|
+
- Critical messages marked failed
|
|
531
|
+
- Manual intervention needed for retries
|
|
532
|
+
- Temporary outages causing permanent failures
|
|
533
|
+
|
|
534
|
+
Why this breaks:
|
|
535
|
+
Default retry behavior (3 attempts, short backoff) works for many cases but
|
|
536
|
+
not all. Some endpoints need more attempts, longer backoff, or different
|
|
537
|
+
strategies. One size doesn't fit all.
|
|
538
|
+
|
|
539
|
+
Recommended fix:
|
|
540
|
+
|
|
541
|
+
# Configure retries per message:
|
|
542
|
+
```typescript
|
|
543
|
+
// Critical operations: more retries, longer backoff
|
|
544
|
+
await qstash.publishJSON({
|
|
545
|
+
url: 'https://myapp.com/api/payment-webhook',
|
|
546
|
+
body: { paymentId: '123' },
|
|
547
|
+
retries: 5,
|
|
548
|
+
// Backoff: 10s, 30s, 1m, 5m, 30m
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Non-critical notifications: fewer retries
|
|
552
|
+
await qstash.publishJSON({
|
|
553
|
+
url: 'https://myapp.com/api/analytics',
|
|
554
|
+
body: { event: 'pageview' },
|
|
555
|
+
retries: 1, // Fail fast, not critical
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
# Consider your endpoint's recovery time:
|
|
560
|
+
- Database down: May need 5+ minutes
|
|
561
|
+
- Third-party API: May need hours
|
|
562
|
+
- Internal service: Usually quick
|
|
563
|
+
|
|
564
|
+
# Use failure callbacks for dead letter handling:
|
|
565
|
+
```typescript
|
|
566
|
+
await qstash.publishJSON({
|
|
567
|
+
url: 'https://myapp.com/api/critical',
|
|
568
|
+
body: data,
|
|
569
|
+
failureCallback: 'https://myapp.com/api/dead-letter',
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Sending large payloads instead of references
|
|
574
|
+
|
|
575
|
+
Severity: MEDIUM
|
|
576
|
+
|
|
577
|
+
Situation: Message contains entire document (5MB). QStash rejects - body too large.
|
|
578
|
+
Even if accepted, slow to transmit. Expensive. Wastes bandwidth.
|
|
579
|
+
|
|
580
|
+
Symptoms:
|
|
581
|
+
- Message publish failures
|
|
582
|
+
- Slow message delivery
|
|
583
|
+
- High bandwidth costs
|
|
584
|
+
|
|
585
|
+
Why this breaks:
|
|
586
|
+
QStash has message size limits (around 500KB body). Large payloads slow
|
|
587
|
+
delivery, increase costs, and can fail entirely. Messages should be
|
|
588
|
+
lightweight triggers, not data carriers.
|
|
589
|
+
|
|
590
|
+
Recommended fix:
|
|
591
|
+
|
|
592
|
+
# Send references, not data:
|
|
593
|
+
```typescript
|
|
594
|
+
// BAD: Large payload
|
|
595
|
+
await qstash.publishJSON({
|
|
596
|
+
url: 'https://myapp.com/api/process',
|
|
597
|
+
body: { document: largeDocumentContent }, // 5MB!
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
// GOOD: Reference only
|
|
601
|
+
await qstash.publishJSON({
|
|
602
|
+
url: 'https://myapp.com/api/process',
|
|
603
|
+
body: { documentId: 'doc_123' }, // Fetch in handler
|
|
604
|
+
});
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
# In your handler:
|
|
608
|
+
```typescript
|
|
609
|
+
export async function POST(req: NextRequest) {
|
|
610
|
+
const { documentId } = await req.json();
|
|
611
|
+
const document = await storage.get(documentId); // Fetch actual data
|
|
612
|
+
await processDocument(document);
|
|
613
|
+
}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
# Large data storage options:
|
|
617
|
+
- S3/R2/Blob storage for files
|
|
618
|
+
- Database for structured data
|
|
619
|
+
- Redis for temporary data (Upstash Redis pairs well)
|
|
620
|
+
|
|
621
|
+
### Not using callback/failureCallback for critical flows
|
|
622
|
+
|
|
623
|
+
Severity: MEDIUM
|
|
624
|
+
|
|
625
|
+
Situation: Important task published. QStash delivers. Endpoint processes. But your
|
|
626
|
+
system doesn't know it succeeded. User stuck waiting. No feedback loop.
|
|
627
|
+
|
|
628
|
+
Symptoms:
|
|
629
|
+
- No visibility into message delivery
|
|
630
|
+
- Users waiting for actions that completed
|
|
631
|
+
- No alerting on failures
|
|
632
|
+
|
|
633
|
+
Why this breaks:
|
|
634
|
+
QStash is fire-and-forget by default. Without callbacks, you don't know
|
|
635
|
+
if messages were delivered successfully. For critical flows, you need
|
|
636
|
+
the feedback loop to update state and handle failures.
|
|
637
|
+
|
|
638
|
+
Recommended fix:
|
|
639
|
+
|
|
640
|
+
# Use callbacks for critical operations:
|
|
641
|
+
```typescript
|
|
642
|
+
await qstash.publishJSON({
|
|
643
|
+
url: 'https://myapp.com/api/send-email',
|
|
644
|
+
body: { userId: '123', template: 'welcome' },
|
|
645
|
+
callback: 'https://myapp.com/api/email-callback',
|
|
646
|
+
failureCallback: 'https://myapp.com/api/email-failed',
|
|
647
|
+
});
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
# Handle the callback:
|
|
651
|
+
```typescript
|
|
652
|
+
// app/api/email-callback/route.ts
|
|
653
|
+
export async function POST(req: NextRequest) {
|
|
654
|
+
// Verify signature first!
|
|
655
|
+
const data = await req.json();
|
|
656
|
+
|
|
657
|
+
// data.sourceMessageId - original message
|
|
658
|
+
// data.status - HTTP status code
|
|
659
|
+
// data.body - response from endpoint
|
|
660
|
+
|
|
661
|
+
await db.emailLogs.update({
|
|
662
|
+
where: { messageId: data.sourceMessageId },
|
|
663
|
+
data: { status: 'delivered' },
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
return NextResponse.json({ received: true });
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
# Failure callback for alerting:
|
|
671
|
+
```typescript
|
|
672
|
+
// app/api/email-failed/route.ts
|
|
673
|
+
export async function POST(req: NextRequest) {
|
|
674
|
+
const data = await req.json();
|
|
675
|
+
await alerting.notify(`Email failed: ${data.sourceMessageId}`);
|
|
676
|
+
await db.emailLogs.update({
|
|
677
|
+
where: { messageId: data.sourceMessageId },
|
|
678
|
+
data: { status: 'failed', error: data.body },
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Cron schedules using wrong timezone
|
|
684
|
+
|
|
685
|
+
Severity: MEDIUM
|
|
686
|
+
|
|
687
|
+
Situation: Scheduled daily report at "9am". But 9am in which timezone? QStash uses UTC.
|
|
688
|
+
Report runs at 4am local time. Users confused. Support tickets filed.
|
|
689
|
+
|
|
690
|
+
Symptoms:
|
|
691
|
+
- Schedules running at unexpected times
|
|
692
|
+
- Off-by-one-hour issues during DST
|
|
693
|
+
- User complaints about report timing
|
|
694
|
+
|
|
695
|
+
Why this breaks:
|
|
696
|
+
QStash cron schedules run in UTC. If you think in local time but configure
|
|
697
|
+
in UTC, schedules will run at unexpected times. This is especially tricky
|
|
698
|
+
with daylight saving time changes.
|
|
699
|
+
|
|
700
|
+
Recommended fix:
|
|
701
|
+
|
|
702
|
+
# QStash uses UTC:
|
|
703
|
+
```typescript
|
|
704
|
+
// This runs at 9am UTC, not local time
|
|
705
|
+
await qstash.schedules.create({
|
|
706
|
+
destination: 'https://myapp.com/api/daily-report',
|
|
707
|
+
cron: '0 9 * * *', // 9am UTC
|
|
708
|
+
});
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
# Convert to UTC:
|
|
712
|
+
- 9am EST = 2pm UTC (winter) / 1pm UTC (summer)
|
|
713
|
+
- 9am PST = 5pm UTC (winter) / 4pm UTC (summer)
|
|
714
|
+
|
|
715
|
+
# Document timezone in schedule name:
|
|
716
|
+
```typescript
|
|
717
|
+
await qstash.schedules.create({
|
|
718
|
+
destination: 'https://myapp.com/api/daily-report',
|
|
719
|
+
cron: '0 14 * * *', // 9am EST (14:00 UTC)
|
|
720
|
+
body: JSON.stringify({
|
|
721
|
+
timezone: 'America/New_York',
|
|
722
|
+
localTime: '9:00 AM',
|
|
723
|
+
}),
|
|
724
|
+
});
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
# Handle DST programmatically if needed:
|
|
728
|
+
Update schedules when DST changes, or accept UTC timing
|
|
729
|
+
|
|
730
|
+
### URL groups with dead or outdated endpoints
|
|
731
|
+
|
|
732
|
+
Severity: MEDIUM
|
|
733
|
+
|
|
734
|
+
Situation: URL group has 5 endpoints. One service deprecated months ago. Messages
|
|
735
|
+
still fan out to it. Failures in dashboard. Wasted attempts. Slower delivery.
|
|
736
|
+
|
|
737
|
+
Symptoms:
|
|
738
|
+
- Failed deliveries in URL groups
|
|
739
|
+
- Messages to deprecated services
|
|
740
|
+
- Slow fan-out due to timeouts
|
|
741
|
+
|
|
742
|
+
Why this breaks:
|
|
743
|
+
URL groups persist until explicitly updated. When services change, endpoints
|
|
744
|
+
become stale. QStash tries to deliver to dead URLs, wastes retries, and
|
|
745
|
+
the failure noise obscures real issues.
|
|
746
|
+
|
|
747
|
+
Recommended fix:
|
|
748
|
+
|
|
749
|
+
# Audit URL groups regularly:
|
|
750
|
+
```typescript
|
|
751
|
+
const groups = await qstash.urlGroups.list();
|
|
752
|
+
for (const group of groups) {
|
|
753
|
+
console.log(`Group: ${group.name}`);
|
|
754
|
+
for (const endpoint of group.endpoints) {
|
|
755
|
+
// Check if endpoint is still valid
|
|
756
|
+
try {
|
|
757
|
+
await fetch(endpoint.url, { method: 'HEAD' });
|
|
758
|
+
console.log(` OK: ${endpoint.url}`);
|
|
759
|
+
} catch {
|
|
760
|
+
console.log(` DEAD: ${endpoint.url}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
# Update groups when services change:
|
|
767
|
+
```typescript
|
|
768
|
+
// Remove dead endpoint
|
|
769
|
+
await qstash.urlGroups.removeEndpoints({
|
|
770
|
+
name: 'order-processors',
|
|
771
|
+
endpoints: [{ url: 'https://old-service.myapp.com/api/process' }],
|
|
772
|
+
});
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
# Automate in CI/CD:
|
|
776
|
+
Check URL group health as part of deployment
|
|
777
|
+
|
|
778
|
+
## Validation Checks
|
|
779
|
+
|
|
780
|
+
### Webhook signature verification
|
|
781
|
+
|
|
782
|
+
Severity: CRITICAL
|
|
783
|
+
|
|
784
|
+
Message: QStash webhook handlers must verify signatures using Receiver
|
|
785
|
+
|
|
786
|
+
Fix action: Add signature verification: const receiver = new Receiver({ currentSigningKey, nextSigningKey }); await receiver.verify({ signature, body, url })
|
|
787
|
+
|
|
788
|
+
### Both signing keys configured
|
|
789
|
+
|
|
790
|
+
Severity: CRITICAL
|
|
791
|
+
|
|
792
|
+
Message: QStash Receiver must have both currentSigningKey and nextSigningKey for key rotation
|
|
793
|
+
|
|
794
|
+
Fix action: Configure both keys: new Receiver({ currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY, nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY })
|
|
795
|
+
|
|
796
|
+
### QStash token hardcoded
|
|
797
|
+
|
|
798
|
+
Severity: CRITICAL
|
|
799
|
+
|
|
800
|
+
Message: QStash token must not be hardcoded - use environment variables
|
|
801
|
+
|
|
802
|
+
Fix action: Use process.env.QSTASH_TOKEN
|
|
803
|
+
|
|
804
|
+
### QStash signing keys hardcoded
|
|
805
|
+
|
|
806
|
+
Severity: CRITICAL
|
|
807
|
+
|
|
808
|
+
Message: QStash signing keys must not be hardcoded
|
|
809
|
+
|
|
810
|
+
Fix action: Use process.env.QSTASH_CURRENT_SIGNING_KEY and process.env.QSTASH_NEXT_SIGNING_KEY
|
|
811
|
+
|
|
812
|
+
### Localhost URL in QStash publish
|
|
813
|
+
|
|
814
|
+
Severity: CRITICAL
|
|
815
|
+
|
|
816
|
+
Message: QStash cannot reach localhost - endpoints must be publicly accessible
|
|
817
|
+
|
|
818
|
+
Fix action: Use a public URL (e.g., your deployed domain or ngrok for testing)
|
|
819
|
+
|
|
820
|
+
### HTTP URL instead of HTTPS
|
|
821
|
+
|
|
822
|
+
Severity: ERROR
|
|
823
|
+
|
|
824
|
+
Message: QStash requires HTTPS URLs for security
|
|
825
|
+
|
|
826
|
+
Fix action: Change http:// to https://
|
|
827
|
+
|
|
828
|
+
### QStash publish without error handling
|
|
829
|
+
|
|
830
|
+
Severity: ERROR
|
|
831
|
+
|
|
832
|
+
Message: QStash publish calls should have error handling for rate limits and failures
|
|
833
|
+
|
|
834
|
+
Fix action: Wrap in try/catch and handle errors appropriately
|
|
835
|
+
|
|
836
|
+
### Using parsed JSON for signature verification
|
|
837
|
+
|
|
838
|
+
Severity: CRITICAL
|
|
839
|
+
|
|
840
|
+
Message: Signature verification requires raw body (req.text()), not parsed JSON
|
|
841
|
+
|
|
842
|
+
Fix action: Use await req.text() to get raw body for verification
|
|
843
|
+
|
|
844
|
+
### Callback endpoint without signature verification
|
|
845
|
+
|
|
846
|
+
Severity: CRITICAL
|
|
847
|
+
|
|
848
|
+
Message: Callback endpoints must also verify signatures - they receive QStash requests too
|
|
849
|
+
|
|
850
|
+
Fix action: Add Receiver signature verification to callback handlers
|
|
851
|
+
|
|
852
|
+
### Schedule without destination URL
|
|
853
|
+
|
|
854
|
+
Severity: ERROR
|
|
855
|
+
|
|
856
|
+
Message: QStash schedules require a destination URL
|
|
857
|
+
|
|
858
|
+
Fix action: Add destination: 'https://your-app.com/api/endpoint' to schedule options
|
|
859
|
+
|
|
860
|
+
## Collaboration
|
|
861
|
+
|
|
862
|
+
### Delegation Triggers
|
|
863
|
+
|
|
864
|
+
- complex workflow|multi-step|state machine -> inngest (Need durable step functions with checkpointing)
|
|
865
|
+
- redis queue|worker process|job priority -> bullmq-specialist (Need traditional queue with workers)
|
|
866
|
+
- ai background|long running ai|model inference -> trigger-dev (Need AI-specific background processing)
|
|
867
|
+
- deploy|vercel|production|environment -> vercel-deployment (Need deployment configuration for QStash)
|
|
868
|
+
- database|persistence|state|sync -> supabase-backend (Need database for job state)
|
|
869
|
+
- auth|user context|session -> nextjs-supabase-auth (Need user context in message handlers)
|
|
870
|
+
|
|
871
|
+
### Serverless Background Jobs
|
|
872
|
+
|
|
873
|
+
Skills: upstash-qstash, nextjs-app-router, vercel-deployment
|
|
874
|
+
|
|
875
|
+
Workflow:
|
|
876
|
+
|
|
877
|
+
```
|
|
878
|
+
1. Define API route handlers (nextjs-app-router)
|
|
879
|
+
2. Configure QStash integration (upstash-qstash)
|
|
880
|
+
3. Deploy with environment vars (vercel-deployment)
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### Reliable Webhooks
|
|
884
|
+
|
|
885
|
+
Skills: upstash-qstash, stripe-integration, supabase-backend
|
|
886
|
+
|
|
887
|
+
Workflow:
|
|
888
|
+
|
|
889
|
+
```
|
|
890
|
+
1. Receive webhooks from Stripe (stripe-integration)
|
|
891
|
+
2. Queue for reliable processing (upstash-qstash)
|
|
892
|
+
3. Persist state to database (supabase-backend)
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### Scheduled Reports
|
|
896
|
+
|
|
897
|
+
Skills: upstash-qstash, email-systems, supabase-backend
|
|
898
|
+
|
|
899
|
+
Workflow:
|
|
900
|
+
|
|
901
|
+
```
|
|
902
|
+
1. Configure cron schedule (upstash-qstash)
|
|
903
|
+
2. Query data for report (supabase-backend)
|
|
904
|
+
3. Send via email system (email-systems)
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
### Fan-out Notifications
|
|
908
|
+
|
|
909
|
+
Skills: upstash-qstash, email-systems, slack-bot-builder
|
|
910
|
+
|
|
911
|
+
Workflow:
|
|
912
|
+
|
|
913
|
+
```
|
|
914
|
+
1. Publish to URL group (upstash-qstash)
|
|
915
|
+
2. Email handler receives (email-systems)
|
|
916
|
+
3. Slack handler receives (slack-bot-builder)
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
### Gradual Migration to Workflows
|
|
920
|
+
|
|
921
|
+
Skills: upstash-qstash, inngest
|
|
922
|
+
|
|
923
|
+
Workflow:
|
|
924
|
+
|
|
925
|
+
```
|
|
926
|
+
1. Start with simple QStash messages (upstash-qstash)
|
|
927
|
+
2. Identify multi-step patterns
|
|
928
|
+
3. Migrate complex flows to Inngest (inngest)
|
|
929
|
+
4. Keep simple schedules in QStash
|
|
930
|
+
```
|
|
67
931
|
|
|
68
932
|
## Related Skills
|
|
69
933
|
|
|
70
934
|
Works well with: `vercel-deployment`, `nextjs-app-router`, `redis-specialist`, `email-systems`, `supabase-backend`, `cloudflare-workers`
|
|
71
935
|
|
|
72
936
|
## When to Use
|
|
73
|
-
|
|
937
|
+
|
|
938
|
+
- User mentions or implies: qstash
|
|
939
|
+
- User mentions or implies: upstash queue
|
|
940
|
+
- User mentions or implies: serverless cron
|
|
941
|
+
- User mentions or implies: scheduled http
|
|
942
|
+
- User mentions or implies: message queue serverless
|
|
943
|
+
- User mentions or implies: vercel cron
|
|
944
|
+
- User mentions or implies: delayed message
|