GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-04-29Advanced

Gemini API × Inngest: Building Fault-Tolerant AI Workflows for Production

A practical guide to building durable, fault-tolerant Gemini API workflows with Inngest — covering retries, fan-out/fan-in, human approval, throttling, and dead-letter patterns.

gemini-api277inngestdurable-executionworkflow9production140

Premium Article

"The Gemini call itself works, but the moment a downstream DB write or notification fails, I have to start the whole job over." I've heard variations of that complaint many times in the last few months. The hard part of putting Gemini into production isn't usually the API call — it's making the surrounding long-running job safely resumable when something halfway through fails.

I've wrestled with this myself for years across my own apps: AdMob webhooks, image-generation pipelines, serverless cron, retry-heavy third-party APIs. After plenty of trial and error, I've landed on Inngest and durable execution. Compared to Cloud Tasks or BullMQ, it's noticeably better-suited to workflows that wrap an external API where "anything can happen mid-call" — that's been my honest experience after about three weeks of running it in production.

This guide walks through concrete code for designing Gemini API workflows that survive partial failures, with practical patterns and pitfalls. The samples are copy-paste runnable, so it'll be easier to follow with an editor open.

Why Gemini API Needs Durable Execution

"Durable execution" sounds like jargon, but the core idea is simple: the engine persists the state of a function mid-run, so when something fails, only the failed step needs to be replayed. Inngest, Temporal, and Restate all share this design. Several realities push Gemini workloads in this direction.

You hit transient HTTP 429s and pause processing if you don't retry properly. Streaming gets cut off and the final chunk silently vanishes. Multimodal inputs (video, large PDFs) timeout mid-analysis. A function-calling result triggers another external API that itself fails for unrelated reasons.

The naive approach — try/catch plus setTimeout — has one painful flaw: every retry burns more tokens. If a 10,000-item generation batch dies on item 5,000, restarting from scratch doubles your bill. Durable execution exists precisely to "replay only what failed," and that's where it earns its keep.

I picked Inngest for three reasons. First, step-level idempotency is enforced at the language level. Anything wrapped in step.run('name', fn) caches its result on success and is skipped on retry. Wrapping each Gemini call in a single step.run is enough to prevent unnecessary re-invocations.

Second, the local dev loop is good. npx inngest-cli@latest dev boots a dashboard that visualizes function steps over time. None of the "works locally, surprises in prod" friction I've felt with Cloud Tasks.

Third, it's TypeScript-first with real type safety. Define your event payloads once and the function side gets autocomplete; mistyped fields surface as build errors instead of runtime mysteries.

Step 1: A Minimal Gemini × Inngest Workflow

Let's start with the smallest realistic flow: receive an article-generation request, generate body text with Gemini, save the result. Next.js App Router setup.

Dependencies. @google/genai is the current official Gemini SDK package as of April 2026; the older @google/generative-ai is deprecated.

npm install @google/genai inngest
npm install -D @types/node typescript

Define the Inngest client and your event types:

// src/inngest/client.ts
import { Inngest, EventSchemas } from "inngest";
 
type Events = {
  "article/generate.requested": {
    data: {
      userId: string;
      topic: string;
      lengthHint: "short" | "medium" | "long";
      requestId: string; // idempotency key
    };
  };
};
 
export const inngest = new Inngest({
  id: "gemilab-content-pipeline",
  schemas: new EventSchemas().fromRecord<Events>(),
});

Always carry a requestId. Downstream code uses it to distinguish a retry of the same logical request from a brand-new one. I prefer minting UUID v7 client-side.

Now the function that calls Gemini:

// src/inngest/functions/generate-article.ts
import { GoogleGenAI } from "@google/genai";
import { inngest } from "../client";
 
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY! });
 
export const generateArticle = inngest.createFunction(
  {
    id: "generate-article",
    // Cap parallelism per user to prevent runaway loops
    concurrency: { key: "event.data.userId", limit: 3 },
    // Exponential backoff retries — Gemini 429s usually clear within seconds
    retries: 4,
  },
  { event: "article/generate.requested" },
  async ({ event, step, logger }) => {
    const { topic, lengthHint, requestId } = event.data;
 
    // Step 1: outline. On retry, only the failed step replays.
    const outline = await step.run("generate-outline", async () => {
      const res = await ai.models.generateContent({
        model: "gemini-2.5-pro",
        contents: `Suggest a 5-H2 article outline for the topic.\nTopic: ${topic}\nLength: ${lengthHint}`,
        config: { temperature: 0.4, responseMimeType: "text/plain" },
      });
      return res.text ?? "";
    });
 
    // Step 2: body. If the outline already succeeded, this resumes from here.
    const body = await step.run("generate-body", async () => {
      const res = await ai.models.generateContent({
        model: "gemini-2.5-pro",
        contents: `Write the full article body following this outline.\n\n${outline}`,
        config: { temperature: 0.6, maxOutputTokens: 8000 },
      });
      return res.text ?? "";
    });
 
    // Step 3: persist (any external call belongs in step.run)
    await step.run("save-to-db", async () => {
      await fetch(`${process.env.API_BASE_URL}/articles`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ requestId, topic, body }),
      });
    });
 
    logger.info("article generated", { requestId, topicLength: topic.length });
    return { requestId, bodyLength: body.length };
  },
);

The single most consequential thing in this file is whether each external call is wrapped in step.run. Without it, every retry re-runs outline generation from scratch. Gemini 2.5 Pro tokens are not cheap, and that bill becomes a real problem within days.

Expose the function through Next.js:

// src/app/api/inngest/route.ts
import { serve } from "inngest/next";
import { inngest } from "@/inngest/client";
import { generateArticle } from "@/inngest/functions/generate-article";
 
export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [generateArticle],
});

Locally, run npx inngest-cli@latest dev and register http://localhost:3000/api/inngest. You can now fire test events from the dashboard and watch each step play out visually.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Anyone struggling with Gemini API timeouts, mid-job failures, and rate limits can now stabilize long-running pipelines using Inngest's automatic retries and step-level idempotency
You'll learn fan-out / fan-in, human-in-the-loop approval gates, throttling, and dead-letter patterns through copy-pasteable TypeScript examples
You'll be able to integrate budget guardrails, observability, and replayable event-driven design into your service starting today
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API / SDK2026-07-18
The Same gemini-flash-latest Pointed to Different Models in Different Regions
Alias resolution rolls out region by region. Send the same gemini-flash-latest to Tokyo and us-central1 and, for a few days, different models answer. Here is why that quietly invalidates your comparisons, and how a one-shot probe catches it.
API / SDK2026-07-15
Sample what you already accepted — an audit budget that catches silent quality drift
A confidence gate only ever looks at output the model hesitated on. Silent drift sinks into the batch that sailed through. Working from a fixed 30-minutes-a-day review budget, this walks through deriving detection time from the binomial, reallocating the same budget across risk strata, and catching slow decay with a cumulative monitor.
API / SDK2026-07-06
Measure a Managed Agent's Behavior Against Fixed Scenarios Before It Reaches Production
The public-preview Managed Agents run autonomously inside an isolated sandbox, so a small prompt or config change can quietly shift their behavior. Diffing the output once, the way you would for a single prompt, is not enough. Here is how to build a regression harness that runs fixed scenarios repeatedly and judges on pass rate, plus a shadow to canary to full promotion with automatic rollback, all with runnable Python.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →