> For the complete documentation index, see [llms.txt](https://developers.docstudio.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.docstudio.com/automation-examples.md).

# Automation Examples

{% hint style="info" %}
The following examples demonstrate how multiple automation types can be combined into workflows for common integration and business-process scenarios.
{% endhint %}

### Send a Chat Notification When an Envelope Is Completed

This example sends a notification when an envelope reaches the `COMPLETED` status. The workflow detects the envelope, builds a JSON message, and sends it through an HTTP request.

The workflow uses the following automation types:

<figure><img src="/files/9orBmvskOsEljIlKFKJo" alt=""><figcaption></figcaption></figure>

#### Build the notification message

The `xslt-converter` transforms `envelope.xml` into the JSON body sent to the external service. The following XSLT creates a Google Chat message containing the envelope subject and status:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:template match="/envelope">
    <xsl:text>{"text": "Envelope \"</xsl:text>
    <xsl:value-of select="info/subject"/>
    <xsl:text>\" is now </xsl:text>
    <xsl:value-of select="state/status"/>
    <xsl:text>"}</xsl:text>
  </xsl:template>
</xsl:stylesheet>
```

The generated Google Chat message has the following format:

```json
{
  "text": "Envelope \"000\" is now COMPLETED"
}
```

#### Configure the target service

Use a Google Chat incoming-webhook URL with the Google Chat message above.

For Telegram, use the `sendMessage` endpoint and include the target `chat_id`:

```xml
<xsl:text>{"chat_id": "-100123456789", "text": "Envelope \"</xsl:text>
<xsl:value-of select="info/subject"/>
<xsl:text>\" is now </xsl:text>
<xsl:value-of select="state/status"/>
<xsl:text>"}</xsl:text>
```

Use the Telegram API URL in this format:

```
https://api.telegram.org/bot<bot-token>/sendMessage
```

{% hint style="warning" %}
Webhook URLs and Telegram bot tokens are secrets. Do not commit production values or include them in shared automation payloads.
{% endhint %}

#### Automation payload

```json
{
  "name": "Notify chat on envelope completion",
  "active": true,
  "workflow": [
    {
      "id": "11111111-1111-1111-1111-111111111111",
      "type": "envelope-trigger",
      "description": "Catch completed envelopes",
      "config": {
        "status": [
          "COMPLETED"
        ]
      },
      "next": [
        "44444444-4444-4444-4444-444444444444"
      ]
    },
    {
      "id": "44444444-4444-4444-4444-444444444444",
      "type": "xslt-converter",
      "description": "Build chat message JSON from envelope fields",
      "config": {
        "filename": "message.json",
        "map": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"><xsl:output method=\"text\"/><xsl:template match=\"/envelope\"><xsl:text>{\"text\": \"Envelope \\\"</xsl:text><xsl:value-of select=\"info/subject\"/><xsl:text>\\\" is now </xsl:text><xsl:value-of select=\"state/status\"/><xsl:text>\"}</xsl:text></xsl:template></xsl:stylesheet>"
      },
      "next": [
        "22222222-2222-2222-2222-222222222222"
      ]
    },
    {
      "id": "22222222-2222-2222-2222-222222222222",
      "type": "api-caller",
      "description": "POST to Google Chat / Telegram",
      "config": {
        "url": "<google-chat-webhook-url-or-telegram-sendMessage-url>",
        "method": "post",
        "contentType": "application/json",
        "timeout": 5000,
        "retries": 3,
        "successCode": 200
      },
      "next": []
    }
  ]
}
```

Set `contentType` to `application/json`. The request then uses the expected content type. For Telegram, use the Telegram URL and XSLT shown above.

#### Verify the notification

Complete an envelope that matches the trigger. Then retrieve the execution and confirm its state is `SUCCEED`.

If the request fails, retrieve the execution log. Check the `api-caller` operation for the response from the chat service.

### Auto-Approve a Vacation Request Based on an External Balance

This example checks an employee's available vacation balance using an external REST API and automatically approves the envelope when enough leave days are available. If the condition is not met, the workflow stops and the envelope remains in the approver's `WAITING` queue for manual processing.

The workflow uses the following automation types:

<figure><img src="/files/f0nExjzWDb1vXWSoxKTA" alt=""><figcaption></figcaption></figure>

#### Build the balance request

The `xslt-converter` creates the request body for the external service from values stored in the vacation request envelope.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:template match="/envelope">
    <xsl:text>{"employeeId": "</xsl:text>
    <xsl:value-of select="//field[@name='employeeId']/@value"/>
    <xsl:text>", "requestedDays": </xsl:text>
    <xsl:value-of select="//field[@name='requestedDays']/@value"/>
    <xsl:text>}</xsl:text>
  </xsl:template>
</xsl:stylesheet>
```

The exact XPath expressions for `employeeId` and `requestedDays` depend on how the fields in the selected template are represented in `envelope.xml`. Verify the field structure using an actual envelope created from the vacation request template before using these expressions.

#### Expected balance response

The balance API must return the available balance and requested days as JSON numbers:

```json
{
  "balance": 12,
  "requested": 5
}
```

The example checks whether `balance` is greater than or equal to `requested`.

#### Automation payload

```json
{
  "name": "Vacation request auto-approval",
  "active": true,
  "workflow": [
    {
      "id": "trigger",
      "type": "envelope-trigger",
      "description": "Catch vacation request envelopes awaiting this mailbox's approval",
      "config": {
        "template": [
          "<vacation-request-template-uuid>"
        ],
        "status": [
          "WAITING"
        ]
      },
      "next": [
        "build-request"
      ]
    },
    {
      "id": "build-request",
      "type": "xslt-converter",
      "description": "Build the balance-check request body from envelope fields",
      "config": {
        "filename": "balance-request.json",
        "map": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"><xsl:output method=\"text\"/><xsl:template match=\"/envelope\"><xsl:text>{\"employeeId\": \"</xsl:text><xsl:value-of select=\"//field[@name='employeeId']/@value\"/><xsl:text>\", \"requestedDays\": </xsl:text><xsl:value-of select=\"//field[@name='requestedDays']/@value\"/><xsl:text>}</xsl:text></xsl:template></xsl:stylesheet>"
      },
      "next": [
        "call-balance-api"
      ]
    },
    {
      "id": "call-balance-api",
      "type": "api-caller",
      "description": "Call external HR/leave-management API (must echo back requestedDays)",
      "config": {
        "url": "https://hr.example.com/api/vacation-balance",
        "method": "post",
        "contentType": "application/json",
        "filename": "balance-response.json",
        "timeout": 5000,
        "retries": 3,
        "successCode": 200
      },
      "next": [
        "if-sufficient"
      ]
    },
    {
      "id": "if-sufficient",
      "type": "if-condition",
      "description": "balance >= requested — gate, no 'else' branch needed",
      "config": {
        "inputFilename": "balance-response.json",
        "executor": "jsonpath",
        "expression": "$[?(@.balance >= @.requested)]"
      },
      "next": [
        "approve"
      ]
    },
    {
      "id": "approve",
      "type": "envelope-approver",
      "description": "Sufficient balance — approve on behalf of this approver mailbox, sending the envelope forward",
      "config": {},
      "next": []
    }
  ]
}
```

The external API must return the data required by the `if-condition` expression. Its response is saved as `balance-response.json` and used as the condition input. When the condition evaluates to true, `envelope-approver` approves the envelope and the envelope continues through its processing flow.

{% hint style="info" %}
When the condition evaluates to false, the workflow does not proceed to `envelope-approver`. The envelope remains in `WAITING` for manual review.
{% endhint %}

The automation must run for the mailbox that holds the pending approver role for the envelope. The `envelope-approver` operation uses this mailbox to perform the same pending approval step that would otherwise be completed manually.

#### Verify the approval

Send a vacation request that matches the trigger. Check the automation execution after the balance API responds.

When the condition passes, verify that the envelope continues after approval. When it fails, verify that the envelope remains `WAITING`.

### AI Review of a Membership Application

This example uses AI to review a membership application and automatically write the generated result back into the envelope. The workflow extracts the applicant's answer, builds an AI prompt, sends it to OpenAI, converts the AI response into XML, and fills the `aiSummary` field in the envelope.

The workflow uses the following automation types:

<figure><img src="/files/nNxlowFFNEeDINZECdac" alt=""><figcaption></figcaption></figure>

#### Template structure

The example template uses two roles and two fields on the same document:

* **Role 0 — applicant** fills `applicationText`.
* **Role 1 — assignee** owns the empty `aiSummary` field that is populated by the automation.

When the applicant sends the envelope with `applicationText` completed, the envelope moves to `WAITING`. The automation catches the envelope when Role 1 becomes active.

#### Build the AI prompt

Use `parameter-extractor` with the `xpath` executor to build a prompt from the value of `applicationText`.

```json
{
  "executor": "xpath",
  "outputParams": [
    "prompt"
  ],
  "expressions": [
    "concat('Summarize this membership application in one sentence: ', //field[@name=\"applicationText\"])"
  ]
}
```

The `prompt` output parameter is passed directly to the next `openai` operation. Because the parameter name matches the `prompt` input expected by `openai`, the `prompt` property can be omitted from the OpenAI operation configuration.

#### Configure the AI operation

The `openai` automation type requires a stored OpenAI credential. Create it first:

```
POST /api/v1/automation/account/{accountId}/credentials
```

```json
{
  "name": "openai-membership-review",
  "type": "openai",
  "data": {
    "apiKey": "<real-key>"
  }
}
```

A successful request returns the credential ID:

```json
{
  "id": "<credential-uuid>"
}
```

Use this value as `credentialsId` in the `openai` workflow item.

The `openai` operation can then be configured as follows:

```json
{
  "type": "openai",
  "config": {
    "model": "gpt-4o",
    "filename": "ai-response.json"
  },
  "credentialsId": "<stored-openai-credential-id>"
}
```

The same workflow can use `gemini` instead of `openai`. The credential type, `credentialsId`, and automation type must be changed accordingly.

#### Convert the AI response

The AI response is JSON, while `envelope-filler` ultimately requires an XML fill payload. Use `json2xml-converter` before the XSLT transformation.

For an OpenAI response with the following shape:

```json
{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "Test AI response text"
      }
    }
  ]
}
```

`json2xml-converter` produces XML in which the response text can be selected with:

```
/root/choices/message/text()
```

Use this XPath in the following `xslt-converter` operation when building the envelope fill payload.

#### Build the envelope fill payload

The `xslt-converter` must create the XML structure expected by the envelope fill operation. The generated payload includes the target field and places the AI response into `aiSummary`.

```xml
<envelope templateUuid="<template-uuid>" templateVersion="<template-version>">
  <info>
    <subject>Membership application</subject>
    <message></message>
    <forwarding delegation="false" sharing="false"/>
  </info>
  <flow>
    <roles>
      <role id="0" mailboxUuid="<applicant-mailbox-uuid>"/>
      <role id="1" mailboxUuid="<automation-mailbox-uuid>"/>
    </roles>
  </flow>
  <documents>
    <document id="0">
      <field name="aiSummary">
        <xsl:value-of select="/root/choices/message/text()"/>
      </field>
    </document>
  </documents>
</envelope>
```

The template UUID, template version, and role mailbox UUIDs are static values for the template used by this automation. The AI-generated response is inserted dynamically into the `aiSummary` field.

#### Automation payload

```json
{
  "name": "AI review of membership application",
  "active": true,
  "workflow": [
    {
      "id": "trigger",
      "type": "envelope-trigger",
      "description": "Catch membership applications awaiting this mailbox's review",
      "config": {
        "template": [
          "<membership-application-template-uuid>"
        ],
        "status": [
          "WAITING"
        ]
      },
      "next": [
        "build-prompt"
      ]
    },
    {
      "id": "build-prompt",
      "type": "parameter-extractor",
      "description": "Build the AI prompt from the applicant's answer",
      "config": {
        "executor": "xpath",
        "outputParams": [
          "prompt"
        ],
        "expressions": [
          "concat('Summarize this membership application in one sentence: ', //field[@name=\"applicationText\"])"
        ]
      },
      "next": [
        "ask-ai"
      ]
    },
    {
      "id": "ask-ai",
      "type": "openai",
      "description": "Generate a summary from the membership application",
      "config": {
        "model": "gpt-4o",
        "filename": "ai-response.json"
      },
      "credentialsId": "<stored-openai-credential-id>",
      "next": [
        "to-xml"
      ]
    },
    {
      "id": "to-xml",
      "type": "json2xml-converter",
      "description": "Convert the AI response from JSON to XML",
      "config": {},
      "next": [
        "build-fill"
      ]
    },
    {
      "id": "build-fill",
      "type": "xslt-converter",
      "description": "Build the envelope fill payload from the AI response",
      "config": {
        "filename": "fill.xml",
        "map": "<xslt stylesheet using /root/choices/message/text() to populate aiSummary>"
      },
      "next": [
        "fill-field"
      ]
    },
    {
      "id": "fill-field",
      "type": "envelope-filler",
      "description": "Write the AI response into aiSummary and complete the assignee step",
      "config": {},
      "next": []
    }
  ]
}
```

When the workflow completes successfully, `envelope-filler` writes the generated text into `aiSummary`. In the verified scenario, Role 1 is the last active role, so filling the field completes the envelope and moves it from `WAITING` to `COMPLETED`.

### Related topics

* [Create an Automation](/create-an-automation.md)
* [Credentials](/credentials.md)
* [Automation Executions and Logs](/automation-executions-and-logs.md)
* [Envelope automation types](/automation-types/envelope.md)
* [Integration automation types](/automation-types/integration.md)
* [Validation and Logic automation types](/automation-types/validation-and-logic.md)
