Writing Functions

Follow along to learn how to:

  • Write a background function triggered by an event
  • Write a scheduled function
  • Write a durable step function anywhere

Writing functions is the same no matter what platform you deploy to — so this example can be used for any TypeScript or JavaScript project (such as Next.js, Remix, or RedwoodJS).

👉 We recommend setting up your API handler first, so you can start testing functions right away. If you haven't done so, check out Serving the Inngest API.

Install the SDK

Firstly, you need to install the SDK via npm or yarn:

npm install inngest  # or yarn add inngest

Writing a background function

Copy the following code into a new file in your project, for example within ./inngest/myFunction.ts

import { Inngest } from "inngest";

const inngest = new Inngest({ name: "My app" });

export default inngest.createFunction(
  { name: "Import product images" },
  { event: "shop/product.imported" }, // The event that will trigger this function

  // This function will be called every time an event payload is received
  async ({ event, step }) => {
    return copyAllImagesToS3(event.data.imageURLs); // You can write whatever you want here.

This defines a new background job which runs any code that you pass it. This function is automatically triggered in the background whenever the shop/product.imported event is received. You can test this function using standard tooling such as Jest or Mocha by exporting the job code and running standard unit tests.

💁 Why event-driven?

Using events to trigger functions has many benefits:

  1. It’s simple: there’s no config to define, and no queues to configure
  2. You can run many functions with a single event (fan-out)
  3. Each event has its own type, so that your data is always correct
  4. Inngest stores each event you receive, making testing and debugging simple

Writing a scheduled function

The following code defines a scheduled function, which runs automatically using the specified cron schedule:

import { Inngest } from "inngest";

const inngest = new Inngest({ name: "My app" });

export default inngest.createFunction(
  { name: "Weekly digest email" }, // The name of your function, used for observability.
  { cron: "TZ=America/New_York 0 9 * * MON" }, // The cron syntax for the function. TZ= is optional.

  // This function will be called on the schedule above
  async ({ step }) => {
    return await sendDigestToAllUsers(); // You can write whatever you want here.

This is very similar to defining an event-driven function, except it uses a cron trigger instead of an event to run your function at the specified interval. This code can be deployed to any platform and will run automatically, without any HTTP calls.

Writing a multi-step function

Any function can become a step function, where each step is individually retriable and tools are provided to coordinate beween events or wait for arbitrary periods of time (up to months)!

Check out the Multi-step functions page for more!

import { Inngest } from "inngest";

const inngest = new Inngest({ name: "My app" });

export default inngest.createFunction(
  { name: "Activation email" },
  { event: "app/user.created" },
  async ({ event, step }) => {
    // Send the user a welcome email
    await step.run("Send welcome email", () =>
      sendEmail({ email: event.data.email, template: "welcome" })

    // Wait for the user to create an order, by waiting and
    // matching on another event
    const order = await step.waitForEvent("app/order.created", {
      match: "data.user.id",
      timeout: "24h",

    if (!order) {
      // User didn't create an order within 24 hours; send
      // them an activation email
      await step.run("Send activation email", async () => {
        // Some code here

Use a multi-step function to create a logical flow of actions triggered from an initial event. It's just like writing a regular synchronous function, but Inngest will step in to help coordinate between events, wait for long periods, or retry failed steps.

In this example, we send the user a welcome email when they are created, then send an activation email after 24 hours if they haven't created an order in that time. Each call to step.run() can be retried independently, meaning a failure in one step won't affect any others.

Coordinating between events like this often requires a lot of code and infrastructure to handle retries, timeouts, state, queues, and many edge cases. Inngest handles all of this for you, so you can focus on writing the code that matters in a clean, readable format.

You can learn more about the Inngest SDK's multi-step functions on our Multi-step functions page.


The Inngest SDK lets you strictly type all of your code against your real production data. This powerful feature helps to:

  • Protect you from making breaking changes
  • Encourage discoverability of your events via autocomplete
  • Give you instant feedback as to whether your code will run as intended


import { Inngest } from "inngest";

type AppUserCreated = {
  name: "app/user.created";
  data: {
    email: string;
  user: {
    id: string;

type Events = {
  "app/user.created": AppUserCreated;

export const inngest = new Inngest<Events>({ name: "My App" });

You can then import this inngest client into where you're creating functions or sending events:

import { inngest } from "./client";

export default inngest.createFunction(
  { name: "Add to Intercom" },
  { event: "app/user.created" },
  async ({ event, step }) => { /* ... */ }

You can learn more about the Inngest SDK's TypeScript support on our Using with TypeScript page.


For Inngest to be able to call your functions, you need to serve them through an API endpoint. There is a more in-depth and pre-framework documentation in here, but after that is set up, you need add all of your functions to your serve handler. For example, in a Next.js project:

// File: ./pages/api/inngest.ts
import { serve } from "inngest/next";
import addToIntercom from "../../inngest/addToIntercom";
import sendWeeklyDigestCron from "../../inngest/sendWeeklyDigestCron";

// You need to serve all functions, event driven or scheduled:
export default serve("My App", [addToIntercom, sendWeeklyDigestCron]);

The serve handler enables Inngest to remotely and securely call your functions running on any platform. Check out those docs for a deeper dive.

Show all served functions

Now that your functions are being served via HTTP, open the route in your browser to view the SDK debug page. It will show you whether your functions are set up correctly, prompt you with what to change if not, and show you any next steps needed along the way!

The SDK's local dashboard showing success