[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build an AI-Powered SaaS with Vercel AI SDK and Modern Stack

Build an AI-Powered SaaS with Vercel AI SDK and Modern Stack

Build a production AI SaaS application using Vercel AI SDK for LLM integration with streaming and tool calling, Trigger.dev for background AI jobs, Resend for transactional emails, Upstash for rate limiting, and SST for deployment — a complete modern stack for launching AI-powered products.

Data & AI#vercel#llm#streaming#typescript#agents
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 90/100·All SAFE
>

ai-sdk

v1.1.0

The Vercel AI SDK is a TypeScript toolkit for calling LLMs from Node.js, Next.js, Nuxt, SvelteKit and edge runtimes through one API. It covers text generation, streaming, structured output with Zod, tool calling, agents (ToolLoopAgent) and chat UIs with useChat. Use when asked to "add AI to my Next.js app", "stream a chat response", "get typed JSON from an LLM", "switch between OpenAI, Anthropic and Gemini", or "build a tool-calling agent".

77/100 quality
5.47× impact
SAFE· stale score
View skill
>

trigger-dev

v1.1.0

Trigger.dev is an open-source background jobs platform for TypeScript: long-running tasks with no timeouts, automatic retries, cron schedules, durable waits, queues and concurrency limits, deployed to Trigger.dev Cloud or self-hosted. Use when the user wants to run background jobs, scheduled tasks or cron in TypeScript, move work off a request handler, retry flaky API calls, wait for hours or days inside a job, or replace BullMQ and Redis.

93/100 quality
8.30× impact
SAFE· stale score
View skill
>

resend

v1.2.0

Resend is an email API for developers: send transactional and marketing email from Node.js, Python and other SDKs, with React Email templates, webhooks for delivery events, batch sending and contact broadcasts. Use when someone asks to "send an email with Resend", "set up Resend webhooks", "build a React Email template", "send a batch of emails" or to move off SendGrid or Mailgun.

93/100 quality
9.80× impact
SAFE· stale score
View skill
>

upstash

v1.1.0

Upstash is a serverless data platform with HTTP-based Redis, rate limiting, QStash message delivery and Workflow for durable functions. Use when adding caching, sessions, rate limiting, delayed jobs, cron schedules or multi-step background workflows to Vercel, Next.js, Cloudflare Workers or AWS Lambda apps that cannot hold persistent connections.

93/100 quality
6.38× impact
SAFE· stale score
View skill
>

sst

v1.1.0

Builds and deploys full-stack applications on AWS with SST (v3 and later, built on Pulumi), defining Lambda functions, API Gateway routes, DynamoDB tables, S3 buckets and Next.js, Remix or Astro sites in sst.config.ts. Use when the user wants to deploy a serverless app to AWS, run sst dev, link resources into functions, manage secrets, or set up per-PR preview stages. Trigger words: sst, serverless stack, sst dev, sst deploy, aws lambda, sst ion, resource linking.

93/100 quality
4.00× impact
SAFE· stale score
View skill
$

Priya is building DocuMind, an AI-powered document analysis SaaS. Users upload contracts, invoices, and reports — the app extracts key information, answers questions about documents, and sends email summaries. She needs streaming AI responses, background processing for large documents, rate limiting for the API, and automated email notifications.

She builds on the hottest 2025/2026 stack: Vercel AI SDK for LLM integration, Trigger.dev for durable background jobs, Resend for beautiful transactional emails, Upstash for serverless rate limiting, and SST for AWS deployment.

Step 1: AI Chat with Streaming and Tool Calling

typescript
// app/api/chat/route.ts — Streaming AI with document tools
import { streamText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
import { Resource } from "sst";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
const ratelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(20, "1m"),  // 20 messages/minute per user
});

export async function POST(req: Request) {
  const { messages, documentId } = await req.json();
  const userId = await getUserId(req);

  // Rate limiting
  const { success, remaining } = await ratelimit.limit(`chat:${userId}`);
  if (!success) {
    return new Response("Rate limit exceeded. Please wait.", { status: 429 });
  }

  // Load document context
  const document = await db.documents.findById(documentId);
  const chunks = await vectorStore.search(
    messages[messages.length - 1].content,
    { filter: { documentId }, topK: 5 },
  );

  const result = streamText({
    model: openai("gpt-4o"),
    system: `You are a document analysis assistant. You have access to the following document:
Title: ${document.title}
Type: ${document.type}

Relevant sections:
${chunks.map(c => c.text).join("\n\n")}

Answer questions accurately based on the document content. Use tools when the user needs specific data extraction.`,
    messages,
    tools: {
      extractKeyTerms: tool({
        description: "Extract key terms, dates, amounts, and parties from the document",
        parameters: z.object({
          extractionType: z.enum(["dates", "amounts", "parties", "obligations", "all"]),
        }),
        execute: async ({ extractionType }) => {
          // Use structured extraction
          const { object } = await generateObject({
            model: openai("gpt-4o"),
            schema: z.object({
              items: z.array(z.object({
                type: z.string(),
                value: z.string(),
                context: z.string(),
                page: z.number().optional(),
              })),
            }),
            prompt: `Extract all ${extractionType} from this document:\n${document.fullText}`,
          });
          return object.items;
        },
      }),
      generateSummary: tool({
        description: "Generate an executive summary of the document",
        parameters: z.object({
          length: z.enum(["brief", "detailed"]).default("brief"),
          focus: z.string().optional().describe("Specific aspect to focus on"),
        }),
        execute: async ({ length, focus }) => {
          const { text } = await generateText({
            model: openai("gpt-4o"),
            prompt: `Write a ${length} executive summary of this document${focus ? `, focusing on ${focus}` : ""}:\n${document.fullText}`,
          });
          return text;
        },
      }),
      emailSummary: tool({
        description: "Email a document summary to the user",
        parameters: z.object({ summary: z.string() }),
        execute: async ({ summary }) => {
          await tasks.trigger("send-document-summary", {
            userId,
            documentId: document.id,
            documentTitle: document.title,
            summary,
          });
          return "Summary email queued for delivery.";
        },
      }),
    },
    maxSteps: 5,
  });

  return result.toDataStreamResponse();
}

Step 2: Background Document Processing

typescript
// trigger/tasks/process-document.ts — Durable background job
import { task, logger, retry } from "@trigger.dev/sdk/v3";
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

export const processDocument = task({
  id: "process-document",
  retry: { maxAttempts: 3 },
  run: async (payload: { documentId: string; userId: string }) => {
    logger.info("Processing document", payload);

    // Step 1: Extract text from PDF
    const document = await db.documents.findById(payload.documentId);
    const text = await extractTextFromPDF(document.s3Key);

    await db.documents.update(payload.documentId, {
      fullText: text,
      status: "text_extracted",
      pageCount: text.split("\f").length,
    });

    // Step 2: Generate embeddings for RAG
    const chunks = splitIntoChunks(text, 500);          // 500 tokens per chunk
    const embeddings = await openai.embeddings.create({
      model: "text-embedding-3-small",
      input: chunks.map(c => c.text),
    });

    await vectorStore.upsert(
      chunks.map((chunk, i) => ({
        id: `${payload.documentId}-${i}`,
        values: embeddings.data[i].embedding,
        metadata: { documentId: payload.documentId, text: chunk.text, page: chunk.page },
      })),
    );

    // Step 3: AI classification and metadata extraction
    const { object: metadata } = await generateObject({
      model: openai("gpt-4o-mini"),
      schema: z.object({
        documentType: z.enum(["contract", "invoice", "report", "legal", "other"]),
        language: z.string(),
        keyDates: z.array(z.object({ label: z.string(), date: z.string() })),
        parties: z.array(z.string()),
        totalAmount: z.number().nullable(),
        summary: z.string(),
      }),
      prompt: `Analyze this document and extract metadata:\n${text.slice(0, 8000)}`,
    });

    await db.documents.update(payload.documentId, {
      ...metadata,
      status: "ready",
      processedAt: new Date(),
    });

    // Step 4: Notify user
    await tasks.trigger("send-document-summary", {
      userId: payload.userId,
      documentId: payload.documentId,
      documentTitle: document.title,
      summary: metadata.summary,
    });

    logger.info("Document processed", { documentId: payload.documentId, type: metadata.documentType });
    return metadata;
  },
});

Step 3: Transactional Emails with Resend

typescript
// trigger/tasks/send-document-summary.ts
import { task } from "@trigger.dev/sdk/v3";
import { Resend } from "resend";
import { DocumentSummaryEmail } from "@/emails/document-summary";

const resend = new Resend(process.env.RESEND_API_KEY);

export const sendDocumentSummary = task({
  id: "send-document-summary",
  run: async (payload: {
    userId: string;
    documentId: string;
    documentTitle: string;
    summary: string;
  }) => {
    const user = await db.users.findById(payload.userId);

    await resend.emails.send({
      from: "DocuMind <notifications@documind.ai>",
      to: user.email,
      subject: `📄 Analysis ready: ${payload.documentTitle}`,
      react: DocumentSummaryEmail({
        userName: user.name,
        documentTitle: payload.documentTitle,
        summary: payload.summary,
        viewUrl: `https://app.documind.ai/documents/${payload.documentId}`,
      }),
      tags: [
        { name: "type", value: "document-summary" },
        { name: "user_id", value: payload.userId },
      ],
    });
  },
});

Step 4: Infrastructure with SST

typescript
// sst.config.ts — Full app infrastructure
export default $config({
  app(input) {
    return { name: "documind", home: "aws" };
  },
  async run() {
    const bucket = new sst.aws.Bucket("Documents");
    const stripeKey = new sst.Secret("StripeKey");
    const openaiKey = new sst.Secret("OpenAIKey");
    const resendKey = new sst.Secret("ResendKey");

    const site = new sst.aws.Nextjs("Web", {
      path: "packages/web",
      link: [bucket, stripeKey, openaiKey, resendKey],
      domain: "app.documind.ai",
    });

    return { url: site.url };
  },
});

Results

After 8 weeks, DocuMind processes 2,000 documents daily for 500 active users.

  • Time to first token: 280ms (streaming AI response via AI SDK)
  • Document processing: Average 12 seconds for a 50-page PDF (Trigger.dev background job)
  • Rate limiting: Zero abuse incidents; Upstash rate limiter handles 50K checks/day at <1ms p99
  • Email delivery: 99.2% delivery rate via Resend; 34% open rate on document summaries
  • Infrastructure cost: $180/month on AWS via SST (Lambda + S3 + RDS) for 500 users
  • LLM cost: $420/month (GPT-4o for chat, GPT-4o-mini for classification; AI SDK provider switching)
  • Zero ops: No servers to manage; SST deploys in 90 seconds, Trigger.dev handles job queues

Related use cases