> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vybe.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating Workflows

> Create scheduled jobs and event-driven functions using the AI chat

You create workflows by describing what you need to the AI in chat. The AI generates the Inngest function, configures the trigger, and wires it into your app.

***

## Creating a workflow with AI

Describe what you need in plain language:

> "Create a workflow that syncs our Salesforce contacts every hour."

> "Set up a daily job that sends a Slack summary of open tickets at 9 AM."

> "Add a workflow that sends a welcome email when a new user is added to the database."

The AI creates the function, sets up the trigger, and registers it. You can view the generated code in the Code tab and test it in the sandbox.

***

## Workflow structure

Workflows are Inngest functions stored in your app's `/inngest/functions` directory. Here is a scheduled workflow example:

```typescript theme={null}
import { inngest } from './client';

export const syncContacts = inngest.createFunction(
  { id: 'sync-contacts' },
  { cron: '0 * * * *' }, // Every hour
  async ({ step }) => {
    const contacts = await step.run('fetch-contacts', async () => {
      // Fetch contacts from Salesforce
      const response = await fetch('https://api.salesforce.com/contacts', {
        headers: {
          Authorization: `Bearer ${process.env.SALESFORCE_TOKEN}`,
        },
      });
      return response.json();
    });

    await step.run('update-database', async () => {
      // Update local database with fetched contacts
      for (const contact of contacts) {
        await db.contact.upsert({
          where: { externalId: contact.id },
          update: { name: contact.name, email: contact.email },
          create: { externalId: contact.id, name: contact.name, email: contact.email },
        });
      }
    });
  }
);
```

Each workflow has three parts:

| Part                | Purpose                                    |
| ------------------- | ------------------------------------------ |
| **Function config** | A unique ID and optional name              |
| **Trigger**         | A cron schedule or an event name           |
| **Handler**         | The logic that executes, broken into steps |

***

## Cron scheduling

Scheduled workflows use cron expressions:

| Expression     | Schedule                             |
| -------------- | ------------------------------------ |
| `* * * * *`    | Every minute                         |
| `*/15 * * * *` | Every 15 minutes                     |
| `0 * * * *`    | Every hour                           |
| `0 9 * * *`    | Daily at 9:00 AM (UTC)               |
| `0 9 * * 1`    | Every Monday at 9:00 AM (UTC)        |
| `0 0 1 * *`    | First day of every month at midnight |

You don't need to memorize cron syntax — just tell the AI when you want it to run:

> "Run this every weekday at 8 AM."

<Tip>
  Cron schedules use UTC time. If your team operates in a specific timezone, mention it in your prompt and the AI will calculate the correct UTC offset.
</Tip>

***

## Event-driven workflows

Event-driven workflows run in response to events emitted by your app:

```typescript theme={null}
import { inngest } from './client';

export const sendWelcomeEmail = inngest.createFunction(
  { id: 'send-welcome-email' },
  { event: 'user/created' },
  async ({ event, step }) => {
    await step.run('send-email', async () => {
      // event.data contains the payload from the event
      const { email, name } = event.data;
      // Send welcome email logic
    });
  }
);
```

To trigger an event-driven workflow, your app emits an event from an API route:

```typescript theme={null}
import { inngest } from '@/inngest/client';

// In an API route
await inngest.send({
  name: 'user/created',
  data: { email: 'jane@example.com', name: 'Jane' },
});
```

The AI sets up both the event emission and the workflow handler when you describe the behavior you want.

***

## Step functions

Workflows are broken into **steps** — discrete units of work managed independently by Inngest. Steps give you:

* **Automatic retries** — Failed steps are retried without re-running earlier steps
* **Durability** — Completed steps are not re-executed on retry
* **Observability** — Each step is tracked separately in the Workflows tab

```typescript theme={null}
async ({ step }) => {
  // Step 1: Fetch data
  const data = await step.run('fetch-data', async () => {
    return await fetchFromExternalApi();
  });

  // Step 2: Process data (only runs if Step 1 succeeds)
  const processed = await step.run('process-data', async () => {
    return transformData(data);
  });

  // Step 3: Save results (only runs if Step 2 succeeds)
  await step.run('save-results', async () => {
    await db.results.createMany({ data: processed });
  });
}
```

Each step should represent a single logical operation — fetching data, transforming it, or saving results.

***

## Error handling and retries

Inngest automatically retries failed steps with exponential backoff, so transient failures like API outages or network timeouts are handled automatically.

If a step continues to fail after all retry attempts, the workflow is marked as failed and error details are visible in the Workflows tab.

For custom error handling, catch errors within the step:

```typescript theme={null}
await step.run('fetch-with-fallback', async () => {
  try {
    return await fetchFromPrimaryApi();
  } catch (error) {
    // Fall back to cached data
    return await fetchFromCache();
  }
});
```

***

## What's next

* [Managing Workflows](/workflows/managing-workflows)
* [Deploying Your App](/deployment/deploying)
