DEV Community

Alex Spinov
Alex Spinov

Posted on

Inngest Has a Free API That Turns Any Function Into a Durable Workflow

Inngest is the durable execution engine for TypeScript. Write functions that survive crashes, retries, and timeouts — automatically.

Define Functions

import { Inngest } from "inngest";

const inngest = new Inngest({ id: "my-app" });

export const scrapeAndNotify = inngest.createFunction(
  { id: "scrape-and-notify", retries: 3 },
  { event: "scrape/requested" },
  async ({ event, step }) => {
    // Step 1: Scrape the URL (retried independently)
    const data = await step.run("scrape-url", async () => {
      const html = await fetch(event.data.url).then(r => r.text());
      return parseProduct(html);
    });

    // Step 2: Save to database
    const saved = await step.run("save-to-db", async () => {
      return db.product.create({ data });
    });

    // Step 3: Send notification
    await step.run("notify", async () => {
      await sendEmail(event.data.email, {
        subject: `Price update: ${data.title}`,
        body: `New price: $${data.price}`,
      });
    });

    return { product: saved };
  }
);
Enter fullscreen mode Exit fullscreen mode

If step 2 fails, step 1 is NOT re-run. Each step is memoized.

Events: Trigger From Anywhere

// From API route
app.post("/api/watch", async (req, res) => {
  await inngest.send({
    name: "scrape/requested",
    data: { url: req.body.url, email: req.body.email },
  });
  res.json({ queued: true });
});

// Batch events
await inngest.send(
  urls.map(url => ({ name: "scrape/requested", data: { url, email: "user@example.com" } }))
);
Enter fullscreen mode Exit fullscreen mode

Scheduled Functions (Cron)

export const dailyPriceCheck = inngest.createFunction(
  { id: "daily-price-check" },
  { cron: "0 8 * * *" }, // 8 AM daily
  async ({ step }) => {
    const watchlist = await step.run("get-watchlist", () =>
      db.watchlistItem.findMany({ where: { active: true } })
    );

    // Fan out: process each item
    const results = await Promise.all(
      watchlist.map(item =>
        step.run(`check-${item.id}`, async () => {
          const current = await scrapePrice(item.url);
          if (current < item.targetPrice) {
            await sendAlert(item.email, item.url, current);
            return { alerted: true, price: current };
          }
          return { alerted: false, price: current };
        })
      )
    );

    return { checked: results.length, alerts: results.filter(r => r.alerted).length };
  }
);
Enter fullscreen mode Exit fullscreen mode

Wait for Events

export const orderWorkflow = inngest.createFunction(
  { id: "order-workflow" },
  { event: "order/created" },
  async ({ event, step }) => {
    await step.run("process-payment", () => chargeCard(event.data.orderId));

    // Wait up to 7 days for shipment confirmation
    const shipment = await step.waitForEvent("wait-for-shipment", {
      event: "order/shipped",
      match: "data.orderId",
      timeout: "7d",
    });

    if (!shipment) {
      await step.run("escalate", () => notifySupport(event.data.orderId));
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

Sleep: Delayed Execution

await step.sleep("wait-1-hour", "1h");
await step.sleepUntil("wait-until-morning", "2026-03-30T08:00:00Z");
Enter fullscreen mode Exit fullscreen mode

Build durable scraping workflows? My Apify tools + Inngest = crash-proof data pipelines.

Custom workflow? Email spinov001@gmail.com

Top comments (0)