opencode-skills-collection 1.0.186 → 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.
Files changed (71) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +5 -1
  2. package/bundled-skills/3d-web-experience/SKILL.md +152 -37
  3. package/bundled-skills/agent-evaluation/SKILL.md +1088 -26
  4. package/bundled-skills/agent-memory-systems/SKILL.md +1037 -25
  5. package/bundled-skills/agent-tool-builder/SKILL.md +668 -16
  6. package/bundled-skills/ai-agents-architect/SKILL.md +271 -31
  7. package/bundled-skills/ai-product/SKILL.md +716 -26
  8. package/bundled-skills/ai-wrapper-product/SKILL.md +450 -44
  9. package/bundled-skills/algolia-search/SKILL.md +867 -15
  10. package/bundled-skills/autonomous-agents/SKILL.md +1033 -26
  11. package/bundled-skills/aws-serverless/SKILL.md +1046 -35
  12. package/bundled-skills/azure-functions/SKILL.md +1318 -19
  13. package/bundled-skills/browser-automation/SKILL.md +1065 -28
  14. package/bundled-skills/browser-extension-builder/SKILL.md +159 -32
  15. package/bundled-skills/bullmq-specialist/SKILL.md +347 -16
  16. package/bundled-skills/clerk-auth/SKILL.md +796 -15
  17. package/bundled-skills/computer-use-agents/SKILL.md +1870 -28
  18. package/bundled-skills/context-window-management/SKILL.md +271 -18
  19. package/bundled-skills/conversation-memory/SKILL.md +453 -24
  20. package/bundled-skills/crewai/SKILL.md +252 -46
  21. package/bundled-skills/discord-bot-architect/SKILL.md +1207 -34
  22. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  23. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  24. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  25. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  26. package/bundled-skills/docs/users/bundles.md +1 -1
  27. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  28. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  29. package/bundled-skills/docs/users/getting-started.md +1 -1
  30. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  31. package/bundled-skills/docs/users/usage.md +4 -4
  32. package/bundled-skills/docs/users/visual-guide.md +4 -4
  33. package/bundled-skills/email-systems/SKILL.md +646 -26
  34. package/bundled-skills/faf-expert/SKILL.md +221 -0
  35. package/bundled-skills/faf-wizard/SKILL.md +252 -0
  36. package/bundled-skills/file-uploads/SKILL.md +212 -11
  37. package/bundled-skills/firebase/SKILL.md +646 -16
  38. package/bundled-skills/gcp-cloud-run/SKILL.md +1117 -32
  39. package/bundled-skills/graphql/SKILL.md +1026 -27
  40. package/bundled-skills/hubspot-integration/SKILL.md +804 -19
  41. package/bundled-skills/idea-darwin/SKILL.md +120 -0
  42. package/bundled-skills/inngest/SKILL.md +431 -16
  43. package/bundled-skills/interactive-portfolio/SKILL.md +342 -44
  44. package/bundled-skills/langfuse/SKILL.md +296 -41
  45. package/bundled-skills/langgraph/SKILL.md +259 -50
  46. package/bundled-skills/micro-saas-launcher/SKILL.md +343 -44
  47. package/bundled-skills/neon-postgres/SKILL.md +572 -15
  48. package/bundled-skills/nextjs-supabase-auth/SKILL.md +269 -21
  49. package/bundled-skills/notion-template-business/SKILL.md +371 -44
  50. package/bundled-skills/personal-tool-builder/SKILL.md +537 -44
  51. package/bundled-skills/plaid-fintech/SKILL.md +825 -19
  52. package/bundled-skills/prompt-caching/SKILL.md +438 -25
  53. package/bundled-skills/rag-engineer/SKILL.md +271 -29
  54. package/bundled-skills/salesforce-development/SKILL.md +912 -19
  55. package/bundled-skills/satori/SKILL.md +54 -0
  56. package/bundled-skills/scroll-experience/SKILL.md +381 -44
  57. package/bundled-skills/segment-cdp/SKILL.md +817 -19
  58. package/bundled-skills/shopify-apps/SKILL.md +1475 -19
  59. package/bundled-skills/slack-bot-builder/SKILL.md +1162 -28
  60. package/bundled-skills/telegram-bot-builder/SKILL.md +152 -37
  61. package/bundled-skills/telegram-mini-app/SKILL.md +445 -44
  62. package/bundled-skills/trigger-dev/SKILL.md +916 -27
  63. package/bundled-skills/twilio-communications/SKILL.md +1310 -28
  64. package/bundled-skills/upstash-qstash/SKILL.md +898 -27
  65. package/bundled-skills/vercel-deployment/SKILL.md +637 -39
  66. package/bundled-skills/viral-generator-builder/SKILL.md +132 -37
  67. package/bundled-skills/voice-agents/SKILL.md +937 -27
  68. package/bundled-skills/voice-ai-development/SKILL.md +375 -46
  69. package/bundled-skills/workflow-automation/SKILL.md +982 -29
  70. package/bundled-skills/zapier-make-patterns/SKILL.md +772 -27
  71. package/package.json +1 -1
@@ -1,23 +1,27 @@
1
1
  ---
2
2
  name: upstash-qstash
3
- description: "You are an Upstash QStash expert who builds reliable serverless messaging without infrastructure management. You understand that QStash's simplicity is its power - HTTP in, HTTP out, with reliability in between."
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: "vibeship-spawner-skills (Apache 2.0)"
6
- date_added: "2026-02-27"
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
- You are an Upstash QStash expert who builds reliable serverless messaging
12
- without infrastructure management. You understand that QStash's simplicity
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
- You've scheduled millions of messages, set up cron jobs that run for years,
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
- Your core philosophy:
20
- 1. HTTP is the universal language - no c
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
- ## Anti-Patterns
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
- ### Skipping Signature Verification
518
+ # Option 3: Use Vercel preview URLs
519
+ Preview deploys give you public URLs for testing
50
520
 
51
- ### Using Private Endpoints
521
+ ### Using default retry behavior for all message types
52
522
 
53
- ### ❌ No Error Handling in Endpoints
523
+ Severity: MEDIUM
54
524
 
55
- ## ⚠️ Sharp Edges
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
- | Issue | Severity | Solution |
58
- |-------|----------|----------|
59
- | Not verifying QStash webhook signatures | critical | # Always verify signatures with both keys: |
60
- | Callback endpoint taking too long to respond | high | # Design for fast acknowledgment: |
61
- | Hitting QStash rate limits unexpectedly | high | # Check your plan limits: |
62
- | Not using deduplication for critical operations | high | # Use deduplication for critical messages: |
63
- | Expecting QStash to reach private/localhost endpoints | critical | # Production requirements: |
64
- | Using default retry behavior for all message types | medium | # Configure retries per message: |
65
- | Sending large payloads instead of references | medium | # Send references, not data: |
66
- | Not using callback/failureCallback for critical flows | medium | # Use callbacks for critical operations: |
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
- This skill is applicable to execute the workflow or actions described in the overview.
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