> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# Using output steps

Output steps turn a flow's results into something that leaves the flow: a rendered document, a stored file, an email, an analytics event, or a message streamed to the client. This page shows how to configure **Render Template**, **Generate PDF**, **Store Asset**, **Send Email**, **Send Event**, and **Send Stream of Data**.

## Before you begin

Open a flow in the **Flow editor**. To add any of these steps, click the **+** control where you want the step, then select the step in the step picker. **Render Template**, **Store Asset**, and **Generate PDF** are in the **Context** group. **Send Email** and **Send Event** are in the **Actions** group. **Send Stream of Data** is in the **Replies** group.

Each step except **Send Stream of Data** writes its result to the **Output variable** that you set. Later steps read it with `{{name}}`. For the variable syntax, see [Flow variables and templates](/user-guide/flows/flow-variables-and-templates).

## Render a document with Render Template

**Render Template** renders a [Liquid](https://shopify.github.io/liquid/) template into HTML, email HTML, markdown, plain text, or a PDF. Use it when a document has loops, conditions, or filters that plain `{{name}}` substitution can't express.

To configure the step, follow these steps:

1. In **Output format**, choose **HTML**, **HTML Email**, **Markdown**, **PDF**, or **Plain Text**.
2. On the **Code** tab, enter the Liquid template.
3. Optional: On the **Sample Data** tab, add a scenario with example JSON. The **Preview** tab renders the template with that data. Sample data is used only for the preview.
4. In **Output variable**, enter a name. The default is `rendered_output`.

By default, the template can read every flow variable by name. To limit the template to specific values, open **Advanced**, click **Add input**, and map a name to an expression such as `{{lookupCustomer.result}}`. When you define inputs, the template sees only those names. An expression that is a single `{{reference}}` keeps its original type, so the template can loop over an array or read an object's fields.

The following template renders an order summary from a flow variable named `order`. Liquid doesn't escape values on its own, so the template adds the `escape` filter to text that a customer typed:

```plaintext
<h1>Order {{ order.id }}</h1>
<ul>
{% for item in order.items %}
  <li>{{ item.name | escape }}: {{ item.price }}</li>
{% endfor %}
</ul>
```

To reuse a block of markup, open **Advanced**, click **Add partial**, name it, and include it with `{% include 'header' %}`. Partials can include other partials up to 8 levels deep. A template can also read a managed secret with `{{secret:NAME}}`.

The output depends on the format:

* **HTML**, **HTML Email**, **Markdown**, and **Plain Text** write the rendered text to the output variable.
* **PDF** writes an object with `contentType`, `bytes` (the PDF as a base64 string), and `byteLength`. To get a shareable URL instead, use **Generate PDF**, or pass `{{rendered_output.bytes}}` to **Store Asset**.

For a PDF, **Advanced** also shows **PDF options**: **Paper size** (**A4** or **Letter**) and **Orientation**. To show the rendered output in a Persona chat, select **Emit as Persona artifact (chat surfaces)**.

A template can be at most 256 KB, and the data that it reads can be at most 1 MB.

## Create a PDF file with Generate PDF

**Generate PDF** renders HTML or markdown as a PDF, stores the file, and returns a URL.

To configure the step, follow these steps:

1. Choose the **HTML** or **Markdown** tab.
2. In **HTML body** or **Markdown body**, enter the content. You can use flow variables, such as `{{rendered_output}}` from a **Render Template** step.
3. Optional: In **Filename**, enter a name such as `invoice-{{order.id}}.pdf`. Without a filename, Runtype names the file `document-` followed by a timestamp.
4. In **Visibility**, choose **Public** or **Private (signed URL)**. For what each option returns, see [Choose a visibility](#choose-a-visibility).
5. Optional: Open **PDF Options** to set the **Page format** (**A4** or **Letter**), **Landscape orientation**, and **Margins**. Margins take CSS sizes, such as `10mm` or `0.5in`.
6. In **Output variable**, enter a name. The default is `pdf_asset`.

The output variable holds an object with `assetId`, `url`, `contentType`, `sizeBytes`, `visibility`, and `expiresIn`. Reference the link as `{{pdf_asset.url}}`.

If the body references a field that doesn't exist on a variable that does, such as `{{order.totl}}` when `order` exists, the step fails instead of printing the placeholder text into the PDF.

## Save a file with Store Asset

**Store Asset** saves a file and returns a URL. Use it to keep a copy of a file from another site, or to store bytes that an earlier step produced.

To configure the step, follow these steps:

1. Choose the source:
   * To copy a file from the web, choose **From URL** and enter the **Source URL**.
   * To store bytes from an earlier step, choose **Inline content**, enter the base64 data in **Base64 content**, and set the **Content type**, such as `application/pdf`. Without a content type, Runtype stores the file as `application/octet-stream`.
2. Optional: In **Filename**, enter the name that browsers use when they download the file.
3. In **Visibility**, choose **Public** or **Private (signed URL)**.
4. In **Output variable**, enter a name. The default is `asset_result`.

The output variable holds the same fields as **Generate PDF**, plus `originalUrl` for a file copied from a URL. A file can be at most 25 MB.

### Choose a visibility

**Store Asset** and **Generate PDF** offer the same two options:

* **Public**: the URL doesn't expire. Anyone with the URL can open the file.
* **Private (signed URL)**: the URL is signed and expires after 1 hour. `expiresIn` holds the lifetime in seconds.

Use **Private (signed URL)** for files that contain customer data, and send the link soon after the step runs.

## Send an email with Send Email

**Send Email** sends an email to one or more recipients.

To configure the step, follow these steps:

1. In **Send email to**, enter one or more addresses separated by commas. You can use variables, such as `{{customer.email}}`.
2. Optional: In **from**, enter a sender address. For the rules, see [Choose a sender](#choose-a-sender).
3. In **subject**, enter the subject line.
4. In **HTML**, enter the email body.
5. Optional: Open **Advanced** to add a **Plain text body (fallback)**, **reply to**, **cc**, and **bcc** addresses, and attachments.
6. In **Output variable**, enter a name. The default is `email_result`.

When the email is sent, the output variable holds `id`, `from`, `to`, `subject`, `status` (`sent`), and `timestamp`.

If the subject or body references a field that doesn't exist on a variable that does, the step fails instead of sending the placeholder text.

### Choose a sender

Leave **from** blank to send from the platform-managed sender. The address is built from the flow's ID, and the step shows it under the field.

To send from your own address, add a Resend API key in **Settings > Integrations**, then enter the address in **from**. The address must be on a domain that you verified in Resend. It can't use the platform-managed sender's domain.

### Add attachments

To add an attachment, open **Advanced**, click **Add Attachment**, and fill in **Filename** and **Content (Text or Base64)**. Runtype detects whether the content is base64. Text content, such as CSV data, is encoded for you.

To attach a variable, enter only the reference, such as `{{csv_data}}`. The attachment content supports a single top-level variable that holds a string. A nested path such as `{{invoice.bytes}}` produces an empty attachment, and text around a reference is sent as written.

To attach a PDF from **Render Template**, copy its bytes into a top-level variable first:

1. Add a **Set Variable** step after **Render Template**.
2. Set the variable name to `invoice_pdf_bytes` and the value to `{{rendered_output.bytes}}`.
3. In the **Send Email** attachment, set **Content (Text or Base64)** to `{{invoice_pdf_bytes}}` and **Filename** to `invoice.pdf`.

To send a link instead of an attachment, use **Generate PDF** and put `{{pdf_asset.url}}` in the email body.

## Track an event with Send Event

**Send Event** sends an analytics event to PostHog. It needs your PostHog Project API Key in **Settings > Integrations**. For setup, plan requirements, and the PostHog region that events go to, see [PostHog integration](/user-guide/settings/integrations-posthog-weaviate-daytona#posthog-integration).

To configure the step, follow these steps:

1. In **Send event**, enter the event name, such as `invoice_sent`.
2. Leave the provider set to **PostHog**.
3. Optional: Under **Event Properties**, click **Add Property** and enter a name and value for each property. Values can use variables and are sent as strings.
4. In **Output variable**, enter a name. The default is `event_result`.

PostHog records the event with the ID of the Runtype user who ran the flow as its `distinct_id`. When PostHog accepts the event, the output variable holds `{ "status": "sent" }`.

## Stream a message with Send Stream of Data

**Send Stream of Data** sends a message to the client while the flow is still running. Use it to show progress, such as "Generating your invoice", before a slow step finishes.

In **Stream message to client**, enter the message. You can use variables, such as `Invoice sent to {{customer.email}}`. The step has no **Output variable** field in the editor. The message is stored in the `message` variable. To use another name, set `outputVariable` in the flow definition.

## Define the steps in code

The following flow definition steps render an invoice, store it as a private PDF, email the link, and record an event. Each object goes in the flow's `steps` array:

```json
[
  {
    "type": "send-stream",
    "name": "Announce",
    "config": { "message": "Preparing invoice {{order.id}}" }
  },
  {
    "type": "template",
    "name": "Render invoice",
    "config": {
      "template": "<h1>Invoice {{ order.id }}</h1>{% for item in order.items %}<p>{{ item.name }}</p>{% endfor %}",
      "outputFormat": "html",
      "outputVariable": "invoice_html"
    }
  },
  {
    "type": "generate-pdf",
    "name": "Create PDF",
    "config": {
      "html": "{{invoice_html}}",
      "filename": "invoice-{{order.id}}.pdf",
      "visibility": "private",
      "pdfOptions": { "format": "Letter" },
      "outputVariable": "invoice_pdf",
      "errorHandling": { "onError": "fail" }
    }
  },
  {
    "type": "send-email",
    "name": "Email customer",
    "config": {
      "to": "{{order.email}}",
      "subject": "Your invoice {{order.id}}",
      "html": "<p>Download your invoice: <a href=\"{{invoice_pdf.url}}\">invoice</a></p>",
      "outputVariable": "email_result",
      "errorHandling": { "onError": "fail" }
    }
  },
  {
    "type": "send-event",
    "name": "Track invoice",
    "config": {
      "provider": "posthog",
      "eventName": "invoice_sent",
      "properties": { "orderId": "{{order.id}}" },
      "outputVariable": "event_result"
    }
  }
]
```

Replace `order` with the name of the flow input or step output that holds the order. **Render Template** uses `"type": "template"`. **Generate PDF** accepts `markdown` in place of `html`, and **Store Asset** takes `url` or `content`, but never both. For how to create and update flows from code, see [Manage flows as code](/user-guide/flows/manage-flows-as-code).

## Handle errors

What happens when an output step fails depends on its error handling mode:

* **Stop on error**: the run ends with an error. **Send Email**, **Send Event**, **Store Asset**, and **Generate PDF** start in this mode when you add them in the **Flow editor**.
* **Continue on error**: the flow keeps going. **Send Email**, **Send Event**, **Store Asset**, and **Generate PDF** write the step's default value to the output variable. With no default value, the output is `{ "status": "failed", "error": "..." }`. **Send Stream of Data** writes its default value, or `null`. A failed **Render Template** step doesn't set its output variable.
* **Use fallbacks**: Runtype retries the step. Add one **Retry** per attempt.

A step in a flow definition that has no `errorHandling` field continues on error. To stop the flow when an email or event fails, set `"errorHandling": { "onError": "fail" }`. For how error handling works across step types, see [Error handling](/user-guide/flows/creating-and-editing-flows#error-handling).

## Troubleshoot output steps

These errors are the most common:

* **`A custom email provider key is required for custom sender addresses`**: **from** has an address, but you haven't added a Resend API key. Add the key in **Settings > Integrations**, or clear **from**.
* **`Custom sender addresses cannot use the managed`** domain: **from** uses the platform-managed sender's domain. Clear **from** to use the managed sender, or use your own domain.
* **`Refusing to send an email containing the literal placeholder text`** or **`Refusing to render a PDF`**: a reference names a field that doesn't exist. Check the spelling of the path in the run's step output.
* **`PostHog API key not configured`**: add your PostHog Project API Key in **Settings > Integrations**.
* **`Template references undefined partial`**: an `{% include %}` tag names a partial that isn't defined in **Advanced**.
* **`Asset exceeds size limit`**: the file is larger than 25 MB.

For more diagnosis steps, see [Flow execution failures](/user-guide/troubleshooting-faq/flow-execution-failures).

## Next steps

Continue with these guides:

* [Flow step types overview](/user-guide/flows/flow-step-types-overview): compare every step type.
* [Flow variables and templates](/user-guide/flows/flow-variables-and-templates): reference step outputs in later steps.
* [Built-in tools](/user-guide/tools/built-in-tools): let an Agent send email or store files with the matching built-in tools.
* [Debugging flows](/user-guide/flows/debugging-flows): inspect each step's output after a run.