# Sending events from functions

In some workflows or pipeline functions, you may want to broadcast events from within your function to trigger *other* functions. This pattern is useful when:

- You want to decouple logic into separate functions that can be re-used across your system
- You want to send an event to [fan-out](/docs-markdown/guides/fan-out-jobs) to multiple other functions
- Your function is handling many items that you want to process in parallel functions
- You want to [delegate tasks to sub-agents asynchronously](/docs-markdown/ai-patterns/sub-agent-delegation#delegate-asynchronously-with-stepsendevent) in an AI agent system
- You want to [cancel](/docs-markdown/guides/cancel-running-functions) another function
- You want to send data to another function [waiting for an event](/docs-markdown/reference/functions/step-wait-for-event)

If your function needs to handle the result of another function, or wait until that other function has completed, you should use [direct function invocation](/docs-markdown/guides/invoking-functions-directly) instead.

## How to send events from functions

To send events from within functions, you will use [`step.sendEvent()`](/docs-markdown/reference/functions/step-send-event). This method takes a single event, or an array of events. The example below uses an array of events.

This is an example of a [scheduled function](/docs-markdown/guides/scheduled-functions) that sends a weekly activity email to all users.

First, the function fetches all users, then it maps over all users to create a `"app/weekly-email-activity.send"` event for each user, and finally it sends all events to Inngest.

```ts
import { GetEvents, Inngest } from "inngest";

const inngest = new Inngest({ id: "signup-flow" });
type Events = GetEvents<typeof inngest>;

export const loadCron = inngest.createFunction(
  { id: "weekly-activity-load-users", triggers: { cron: "0 12 * * 5" } },
  async ({ event, step }) => {
    // Fetch all users
    const users = await step.run("fetch-users", async () => {
      return fetchUsers();
    });

    // For each user, send us an event.  Inngest supports batches of events
    // as long as the entire payload is less than 512KB.
    const events = users.map<Events["app/weekly-email-activity.send"]>(
      (user) => {
        return {
          name: "app/weekly-email-activity.send",
          data: {
            ...user,
          },
          user,
        };
      }
    );

    // Send all events to Inngest, which triggers any functions listening to
    // the given event names.
    await step.sendEvent("fan-out-weekly-emails", events);

    // Return the number of users triggered.
    return { count: users.length };
  }
);
```

Next, create a function that listens for the `"app/weekly-email-activity.send"` event. This function will be triggered for each user that was sent an event in the previous function.

```ts
export const sendReminder = inngest.createFunction(
  { id: "weekly-activity-send-email", triggers: { event: "app/weekly-email-activity.send" } },
  async ({ event, step }) => {
    const data = await step.run("load-user-data", async () => {
      return loadUserData(event.data.user.id);
    });

    await step.run("email-user", async () => {
      return sendEmail(event.data.user, data);
    });
  }
);
```

Each of these functions will run in parallel and individually retry on error, resulting in a faster, more reliable system.

> **Callout:** 💡 Tip: When triggering lots of functions to run in parallel, you will likely want to configure concurrency limits to prevent overloading your system. See our concurrency guide for more information.

### Why `step.sendEvent()` vs. `inngest.send()`?

By using [`step.sendEvent()`](/docs-markdown/reference/functions/step-send-event) Inngest's SDK can automatically add context and tracing which ties events to the current function run. If you use [`inngest.send()`](/docs-markdown/reference/events/send), the context around the function run is not present.

To send events from within functions, you  will use [`step.Send()`](https://pkg.go.dev/github.com/inngest/inngestgo/step#Send) or [`step.SendMany()`](https://pkg.go.dev/github.com/inngest/inngestgo/step#SendMany)

This is an example of a [scheduled function](/docs-markdown/guides/scheduled-functions) that sends a weekly activity email to all users.

First, the function fetches all users, then it maps over all users to create a `"app/weekly-email-activity.send"` event for each user, and finally it sends all events to Inngest.

```go
import (
	"context"

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

func loadCronInngestFn(client inngestgo.Client) (inngestgo.ServableFunction, error) {
	return inngestgo.CreateFunction(
		client,
		inngestgo.FunctionOpts{
			ID: "weekly-activity-load-users",
		},
		inngestgo.CronTrigger("0 12 * * 5"),
		func(ctx context.Context, input inngestgo.Input[any]) (any, error) {
			// Fetch all users
			users, err := step.Run(ctx, "fetch-users", func(ctx context.Context) ([]User, error) {
				return fetchUsers()
			})
			if err != nil {
				return nil, err
			}

			// For each user, send us an event. Inngest supports batches of events
			// as long as the entire payload is less than 512KB.
			events := make([]inngestgo.Event, len(users))
			for i, user := range users {
				events[i] = inngestgo.Event{
					Name: "app/weekly-email-activity.send",
					Data: map[string]interface{}{
						"user": user,
					},
				}
			}

			// Send all events to Inngest, which triggers any functions listening to
			// the given event names.
			_, err = step.SendMany(ctx, "send-events", events)
			if err != nil {
				return nil, err
			}

			// Return the number of users triggered
			return map[string]any{
				"count": len(users),
			}, nil
		},
	)
}
```

Next, create a function that listens for the `"app/weekly-email-activity.send"` event. This function will be triggered for each user that was sent an event in the previous function.

```go
import (
	"context"

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

type User struct {
	ID string
}

type Data struct {
	User User
}

func loadSendReminderInngestFn(client inngestgo.Client) (inngestgo.ServableFunction, error) {
	return inngestgo.CreateFunction(
		client,
		inngestgo.FunctionOpts{
			ID: "weekly-activity-send-email",
		},
		inngestgo.EventTrigger("app/weekly-email-activity.send", nil),
		func(ctx context.Context, input inngestgo.Input[Data]) (any, error) {
			data, err := step.Run(ctx, "load-user-data", func(ctx context.Context) (any, error) {
				return loadUserData(input.Event.Data.User.ID)
			})
			if err != nil {
				return nil, err
			}

			_, err = step.Run(ctx, "email-user", func(ctx context.Context) (any, error) {
				return SendEmail(SendEmailInput{
					To:      input.Event.Data.User.ID,
					Subject: "Welcome to Inngest!",
					Message: GenerateWelcomeEmailForUser(data),
				})
			})
			if err != nil {
				return nil, err
			}

			return nil, nil
		},
	)
}
```

Each of these functions will run in parallel and individually retry on error, resulting in a faster, more reliable system.

> **Callout:** 💡 Tip: When triggering lots of functions to run in parallel, you will likely want to configure concurrency limits to prevent overloading your system. See our concurrency guide for more information.

To send events from within functions, you will use [`step.send_event()`](/docs-markdown/reference/python/steps/send-event). This method takes a single event, or an array of events. The example below uses an array of events.

This is an example of a [scheduled function](/docs-markdown/guides/scheduled-functions) that sends a weekly activity email to all users.

First, the function fetches all users, then it maps over all users to create a `"app/weekly-email-activity.send"` event for each user, and finally it sends all events to Inngest.

```py
import inngest
from src.inngest.client import inngest_client

@inngest_client.create_function(
    fn_id="weekly-activity-load-users",
    trigger=inngest.TriggerCron(cron="0 12 * * 5")
)
async def load_cron(ctx: inngest.Context):
    # Fetch all users
    async def fetch():
        return await fetch_users()
    
    users = await ctx.step.run("fetch-users", fetch)

    # For each user, send us an event. Inngest supports batches of events
    # as long as the entire payload is less than 512KB.
    events = []
    for user in users:
        events.append(
            inngest.Event(
                name="app/weekly-email-activity.send",
                data={
                    **user,
                    "user": user
                }
            )
        )

    # Send all events to Inngest, which triggers any functions listening to
    # the given event names.
    await ctx.step.send_event("fan-out-weekly-emails", events)

    # Return the number of users triggered.
    return {"count": len(users)}
```

Next, create a function that listens for the `"app/weekly-email-activity.send"` event. This function will be triggered for each user that was sent an event in the previous function.

```py
@inngest_client.create_function(
    fn_id="weekly-activity-send-email",
    trigger=inngest.TriggerEvent(event="app/weekly-email-activity.send")
)
async def send_reminder(ctx: inngest.Context):
    async def load_data():
        return await load_user_data(ctx.event.data["user"]["id"])
    
    data = await ctx.step.run("load-user-data", load_data)

    async def send():
        return await send_email(ctx.event.data["user"], data)
    
    await ctx.step.run("email-user", send)
```

Each of these functions will run in parallel and individually retry on error, resulting in a faster, more reliable system.

> **Callout:** 💡 Tip: When triggering lots of functions to run in parallel, you will likely want to configure concurrency limits to prevent overloading your system. See our concurrency guide for more information.

### Why `step.send_event()` vs. `inngest.send()`?

By using [`step.send_event()`](/docs-markdown/reference/python/steps/send-event) Inngest's SDK can automatically add context and tracing which ties events to the current function run. If you use [`inngest.send()`](/docs-markdown/reference/python/client/send), the context around the function run is not present.

## Parallel functions vs. parallel steps

Another technique similar is running multiple steps in parallel (read the [step parallelism guide](/docs-markdown/guides/step-parallelism)). Here are the key differences:

- Both patterns run code in parallel
- With parallel steps, you can access the output of each step, whereas with the above example, you cannot
- Parallel steps have limit of 1,000 steps, though you can trigger as many functions as you'd like using the send event pattern
- Decoupled functions can be tested and [replayed](/docs-markdown/platform/replay) separately, whereas parallel steps can only be tested as a whole
- You can retry individual functions easily if they permanently fail, whereas if a step permanently fails (after retrying) the function itself will fail and terminate.

## Sending events vs. invoking

A related pattern is invoking external functions directly instead of just triggering them with an event. See the [Invoking functions directly](/docs-markdown/guides/invoking-functions-directly) guide. Here are some key differences:

- Sending events from functions is better suited for parallel processing of independent tasks and invocation is better for coordinated, interdependent functions
- Sending events can be done in bulk, whereas invoke can only invoke one function at a time.
- Sending events can be combined with [fan-out](/docs-markdown/guides/fan-out-jobs) to trigger multiple functions from a single event
- Unlike invocation, sending events will not receive the result of the invoked function