# Fan-out

> Start independent functions from one event so one failure does not stop the other work.

Send one signup event to start a welcome email and trial setup in separate runs. Each function keeps its own retries and trace, so a failure in one does not stop the other.
For example, a signup event can start a welcome email and a trial setup at the same time.

## Send one event

Send the event after your app creates the user:

```typescript {{ title: "TypeScript" }}
await inngest.send({
  name: "app/user.signed_up",
  data: { userId: user.id, email: user.email },
});
```

```python {{ title: "Python" }}
await inngest_client.send(
    inngest.Event(
        name="app/user.signed_up",
        data={"userId": user.id, "email": user.email},
    )
)
```

```go {{ title: "Go" }}
_, err := client.Send(ctx, inngestgo.Event{
	Name: "app/user.signed_up",
	Data: map[string]any{"userId": user.ID, "email": user.Email},
})
```

The event starts every registered function whose trigger matches `app/user.signed_up`.

## Create independent functions

This TypeScript v4 example uses the same typed event for both functions. Assume `inngest`, `sendWelcomeEmail`, and `createTrial` are defined by your app.

```typescript {{ title: "TypeScript" }}
import { eventType, staticSchema } from "inngest";

const userSignedUp = eventType("app/user.signed_up", {
  schema: staticSchema<{ userId: string; email: string }>(),
});

export const welcomeEmail = inngest.createFunction(
  { id: "send-welcome-email", triggers: [userSignedUp] },
  async ({ event, step }) => {
    await step.run("send-email", () =>
      sendWelcomeEmail(event.data.email)
    );
  }
);

export const trialSetup = inngest.createFunction(
  { id: "start-trial", triggers: [userSignedUp] },
  async ({ event, step }) => {
    await step.run("create-trial", () =>
      createTrial(event.data.userId)
    );
  }
);
```

```python {{ title: "Python" }}
import inngest
import pydantic

class UserSignedUp(pydantic.BaseModel):
    userId: str
    email: str

@inngest_client.create_function(
    fn_id="send-welcome-email",
    trigger=inngest.TriggerEvent(event="app/user.signed_up"),
)
async def welcome_email(ctx: inngest.Context) -> None:
    data = UserSignedUp.model_validate(ctx.event.data)

    async def send_email() -> None:
        await send_welcome_email(data.email)

    await ctx.step.run("send-email", send_email)

@inngest_client.create_function(
    fn_id="start-trial",
    trigger=inngest.TriggerEvent(event="app/user.signed_up"),
)
async def trial_setup(ctx: inngest.Context) -> None:
    data = UserSignedUp.model_validate(ctx.event.data)

    async def start_trial() -> None:
        await create_trial(data.userId)

    await ctx.step.run("create-trial", start_trial)
```

```go {{ title: "Go" }}
import (
	"context"

	"github.com/inngest/inngestgo"
	"github.com/inngest/inngestgo/step"
)

type UserSignedUpData struct {
	UserID string `json:"userId"`
	Email  string `json:"email"`
}

func WelcomeEmail(client inngestgo.Client) (inngestgo.ServableFunction, error) {
	return inngestgo.CreateFunction(
		client,
		inngestgo.FunctionOpts{ID: "send-welcome-email"},
		inngestgo.EventTrigger("app/user.signed_up", nil),
		func(ctx context.Context, input inngestgo.Input[UserSignedUpData]) (any, error) {
			_, err := step.Run(ctx, "send-email", func(ctx context.Context) (any, error) {
				return nil, sendWelcomeEmail(ctx, input.Event.Data.Email)
			})
			return nil, err
		},
	)
}

func TrialSetup(client inngestgo.Client) (inngestgo.ServableFunction, error) {
	return inngestgo.CreateFunction(
		client,
		inngestgo.FunctionOpts{ID: "start-trial"},
		inngestgo.EventTrigger("app/user.signed_up", nil),
		func(ctx context.Context, input inngestgo.Input[UserSignedUpData]) (any, error) {
			_, err := step.Run(ctx, "create-trial", func(ctx context.Context) (any, error) {
				return nil, createTrial(ctx, input.Event.Data.UserID)
			})
			return nil, err
		},
	)
}
```

Register both functions in your app. Inngest starts a separate run for each matching function. A failure in trial setup does not stop the welcome email. You can inspect and recover the failed run without rerunning the other function.

## Choose the right shape

- **One event, several functions:** Use this pattern when the work has separate owners, retry needs, or destinations. Functions can live in different apps and use different language SDKs.
- **One function, parallel steps:** Keep related results in one run when later steps need them.
- **One function, many items:** Process items in a loop when one run should track the sequence. See [Working with loops](/docs-markdown/durable-execution/guides-and-advanced/patterns/working-with-loops).
  To fan out from inside an Inngest function, send new events with [`step.sendEvent()`](/docs-markdown/durable-execution/primitives/step-sendevent). Use `step.invoke()` when the caller needs a child function's result. For high-volume fan-out, check [Limits](/docs-markdown/durable-execution/limits) and [Flow control](/docs-markdown/durable-execution/flow-control) before choosing event and concurrency settings.

## Related pages

- [Send events](/docs-markdown/durable-execution/guides-and-advanced/events-and-triggers/send-events) explains sending events.
- [Idempotency](/docs-markdown/durable-execution/guides-and-advanced/idempotency) explains how to make external writes safe when a step retries.