---
title: Custom automation triggers
related:
  - https://docs.kentico.com/documentation/business-users/digital-marketing/automation.md
  - https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/automation-customization/automation-custom-steps.md
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

> **License:** Advanced license required.
>
> Features described on this page require the Xperience by Kentico **Advanced** license tier.

Xperience by Kentico allows developers to create **custom triggers** that start marketing [automation](https://docs.kentico.com/documentation/business-users/digital-marketing/automation.md) processes from application code. Custom triggers appear in the Automation Builder UI alongside the built-in triggers (Form submission, Registration, Custom activity) and can be selected by marketers when creating processes.

Use custom triggers when you need to start automation processes based on events that occur in your application, such as:

- A purchase is completed in your [digital commerce](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup.md) system
- A [contact](https://docs.kentico.com/documentation/business-users/digital-marketing/contact-management.md) is updated (e.g., in a specific field)
- A webhook from an external application is received
- A regularly [scheduled task](https://docs.kentico.com/documentation/developers-and-admins/customization/scheduled-tasks.md) runs (allows you to start a process for a set of contacts matching certain criteria)

> **Tip:** **AI-assisted trigger development**
>
> The `automation-trigger` skill in the [digital experience plugin](https://github.com/Kentico/xperience-by-kentico-kenticopilot/tree/main/plugins/kentico-digital-experience) from [KentiCopilot](https://docs.kentico.com/guides/development/kenticopilot.md) generates triggers that follow the patterns on this page, including [properties](#create-custom-triggers-with-properties), registration, and [trigger firing](#fire-triggers-from-code) in your application code. The skill studies the components your project already has and mirrors their conventions.

## Overview

Custom triggers are classes implemented according to one of the following patterns:

- [Triggers without data or properties](#create-custom-triggers-without-data) – basic triggers that inherit from `AutomationTrigger`.
- [Triggers with typed data](#create-custom-triggers-with-typed-data) – inherit from `AutomationTrigger<TData>`. Such triggers carry a typed data payload (implementing `IAutomationTriggerData`) that can be accessed by [custom steps](https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/automation-customization/automation-custom-steps.md) in the process.
- [Triggers with data and properties](#create-custom-triggers-with-properties) – inherit from `AutomationTrigger<TData, TProperties>`. Such triggers additionally have properties configured by marketers in the Automation Builder UI, which can be used to evaluate whether the process should start when the trigger is fired.

> **Key:** **Key points**
>
> - Fire triggers from code by injecting `IAutomationTriggerDispatcher` and calling `FireTrigger<TTrigger>`.
> - Triggers operate on [contacts](https://docs.kentico.com/documentation/business-users/digital-marketing/contact-management.md) – the object you fire a trigger against must be a `ContactInfo`.
> - You can override the `Evaluate` method in trigger classes to control whether a process starts when the trigger is fired. Depending on the trigger's base class, you can evaluate using the trigger's data and/or properties configured by marketers.
> - Register triggers via the `RegisterAutomationTrigger<TTrigger>` assembly attribute.
> - Trigger classes must be stateless. When a trigger is fired, the system creates and reuses a single instance of the trigger class to evaluate and start all processes with the given trigger.

![Overview of custom automation trigger components](https://docs.kentico.com/docsassets/documentation/automation-custom-triggers/automation_triggers_overview.drawio.svg "Overview of custom automation trigger components")

## Create custom triggers without data

To create a trigger that starts a process without using or passing additional data:

1. Create a class inheriting from `AutomationTrigger`.
   - You can optionally override the `Evaluate` method to dynamically control whether the process starts. See [Control process start with Evaluate](#control-process-start-with-evaluate).
   - Trigger classes must be stateless. When a trigger is fired, the system creates and reuses a single instance of the trigger class to evaluate and start all processes with the given trigger.
2. [Register the trigger](#register-custom-triggers) via the `RegisterAutomationTrigger<TTrigger>` assembly attribute.

```csharp title="Example - Purchase trigger"
using CMS.Automation;
using Kentico.Xperience.Admin.Base;

using Acme.Automation;

[assembly: RegisterAutomationTrigger<PurchaseTrigger>(
    identifier: PurchaseTrigger.IDENTIFIER,
    displayName: "Purchase completed",
    Description = "Fires when a customer completes a purchase.",
    IconName = Icons.ShoppingCart)]

namespace Acme.Automation;

public class PurchaseTrigger : AutomationTrigger
{
    public const string IDENTIFIER = "Acme.PurchaseTrigger";
}
```

Marketers can select the trigger when designing [automation processes](https://docs.kentico.com/documentation/business-users/digital-marketing/automation.md).

All processes using the trigger start whenever you [fire the trigger](#fire-triggers-from-code) from your application's code (depending on the [recurrence settings](https://docs.kentico.com/documentation/business-users/digital-marketing/automation.md#automation-process-recurrence) of the given process).

## Create custom triggers with typed data

To create a trigger that starts a process and provides structured data to steps:

1. Create a data class implementing the `IAutomationTriggerData` interface.
   - Provide a stable, unique `Identifier`. We recommend using a unique prefix in your identifiers to prevent conflicts. The system uses the identifier internally to store and retrieve the trigger data payload within automation processes. Never change the value once the trigger is in use.
   - Add properties to hold the data you want to capture when the trigger fires.
2. Create a trigger class inheriting from `AutomationTrigger<TData>`, where `TData` is your data class.
   - You can optionally override the `Evaluate` method to dynamically control whether the process starts. The method has a `TData` parameter providing an instance of the data class populated with values set when the trigger was fired. See [Control process start with Evaluate](#control-process-start-with-evaluate).
   - Trigger classes must be stateless. When a trigger is fired, the system creates and reuses a single instance of the trigger class to evaluate and start all processes with the given trigger.
3. [Register the trigger](#register-custom-triggers) via the `RegisterAutomationTrigger<TTrigger>` assembly attribute.

```csharp title="Example - Purchase trigger data"
using CMS.Automation;

namespace Acme.Automation;

public class PurchaseTriggerData : IAutomationTriggerData
{
    // Stable, unique identifier of the trigger data type
    public string Identifier => "Acme.PurchaseTriggerData";

    public string OrderId { get; init; }

    public decimal TotalAmount { get; init; }

    public string Currency { get; init; }
}
```

```csharp title="Example - Purchase trigger with data"
using System.Threading;
using System.Threading.Tasks;

using CMS.Automation;
using Kentico.Xperience.Admin.Base;

using Acme.Automation;

[assembly: RegisterAutomationTrigger<HighValuePurchaseTrigger>(
    identifier: HighValuePurchaseTrigger.IDENTIFIER,
    displayName: "Purchase over 100",
    Description = "Fires when a customer completes a purchase with an order total of 100 or more.",
    IconName = Icons.ShoppingCart)]

namespace Acme.Automation;

public class HighValuePurchaseTrigger : AutomationTrigger<PurchaseTriggerData>
{
    public const string IDENTIFIER = "Acme.HighValuePurchaseTrigger";

    public const decimal MINIMUM_PURCHASE_AMOUNT = 100.00m;

    public override Task<bool> Evaluate(
        AutomationTriggerContext context,
        PurchaseTriggerData triggerData,
        CancellationToken cancellationToken)
    {
        // In this example, the trigger fires only if the purchase total meets a minimum requirement
        return Task.FromResult(triggerData.TotalAmount >= MINIMUM_PURCHASE_AMOUNT);
    }
}
```

Marketers can select the trigger when designing [automation processes](https://docs.kentico.com/documentation/business-users/digital-marketing/automation.md).

All processes using the trigger start whenever you [fire the trigger](#fire-triggers-from-code) from your application's code (depending on the [recurrence settings](https://docs.kentico.com/documentation/business-users/digital-marketing/automation.md#automation-process-recurrence) of the given process). When firing the trigger, prepare a trigger data payload and use the `AutomationTriggerDispatch` constructor overload that accepts trigger data.

The trigger data is JSON-serialized and stored with the automation process state. [Custom steps](https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/automation-customization/automation-custom-steps.md) within the process can read the trigger data via `AutomationProcessContext.GetTriggerData<T>`. See [Access trigger data](https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/automation-customization/automation-custom-steps.md#access-trigger-data).

## Create custom triggers with properties

To create a trigger that allows marketers to configure the process start conditions in the [Automation Builder UI](https://docs.kentico.com/documentation/business-users/digital-marketing/automation.md):

1. Create a properties class implementing the `IAutomationTriggerProperties` interface.

2. Define each property by adding a C# property in the class.

3. Assign [editing components](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-form-components/editing-components.md) to define the visual interface for properties in the trigger's configuration dialog.

   - See [Step properties](https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/automation-customization/automation-custom-steps.md#step-properties) and [Property guidelines](https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/automation-customization/automation-custom-steps.md#property-guidelines) for detailed information. Trigger properties provide the same UI form component options as custom step properties.

   ```csharp title="Example - Purchase trigger properties"
   using CMS.Automation;
   using Kentico.Xperience.Admin.Base.FormAnnotations;

   namespace Acme.Automation;

   public class PurchaseTriggerProperties : IAutomationTriggerProperties
   {
       // Minimum order total to start the automation process
       [DecimalNumberInputComponent(Label = "Minimum order total", Order = 10)]
       [RequiredValidationRule]
       public decimal MinimumTotal { get; set; } = 100.00m;
   }
   ```

4. (Optional) Create a data class implementing the `IAutomationTriggerData` interface.
   - See [Create custom triggers with typed data](#create-custom-triggers-with-typed-data) for more information.

5. Create a trigger class inheriting from `AutomationTrigger<TData, TProperties>`, where `TProperties` is your properties class and `TData` your data class.
   - If you wish to have a trigger with configurable properties, but without trigger data, create a dummy class implementing `IAutomationTriggerData` (with only the required `Identifier` member) to use as the `TData` parameter.
   - Trigger classes must be stateless. When a trigger is fired, the system creates and reuses a single instance of the trigger class to evaluate and start all processes with the given trigger.

6. Override the `Evaluate` method to control whether the process starts based on the configured properties.
   - The method receives the properties instance populated with values from the trigger's configuration.
   - The method has a `TData` parameter providing an instance of the data class populated with values set when the trigger was fired.
   - See [Control process start with Evaluate](#control-process-start-with-evaluate).

7. [Register the trigger](#register-custom-triggers) via the `RegisterAutomationTrigger<TTrigger>` assembly attribute.

```csharp title="Example - Purchase trigger with properties"
using System.Threading;
using System.Threading.Tasks;

using CMS.Automation;
using Kentico.Xperience.Admin.Base;

using Acme.Automation;

[assembly: RegisterAutomationTrigger<PurchaseWithMinimumTotalTrigger>(
    identifier: PurchaseWithMinimumTotalTrigger.IDENTIFIER,
    displayName: "Purchase with minimum total",
    Description = "Fires when a customer completes a purchase with an order total greater than a specified value.",
    IconName = Icons.ShoppingCart)]

namespace Acme.Automation;

public class PurchaseWithMinimumTotalTrigger : AutomationTrigger<PurchaseTriggerData, PurchaseTriggerProperties>
{
    public const string IDENTIFIER = "Acme.PurchaseWithMinimumTotalTrigger";

    public override Task<bool> Evaluate(
        AutomationTriggerContext context,
        PurchaseTriggerProperties triggerProperties,
        PurchaseTriggerData triggerData,
        CancellationToken cancellationToken)
    {
        // In this example, the trigger fires only if the purchase total
        // is greater than a value specified in the trigger properties
        return Task.FromResult(triggerData.TotalAmount >= triggerProperties.MinimumTotal);
    }
}
```

Marketers can select the trigger when designing [automation processes](https://docs.kentico.com/documentation/business-users/digital-marketing/automation.md) and configure its properties directly in the Automation Builder UI.

When you [fire the trigger](#fire-triggers-from-code) from your application's code, the trigger's `Evaluate` method runs for all processes using the trigger and their property configuration. If the method returns `true`, the given process starts (also depending on the [recurrence settings](https://docs.kentico.com/documentation/business-users/digital-marketing/automation.md#automation-process-recurrence) of the process).

## Control process start with Evaluate

Every trigger base class exposes an overridable `Evaluate` method that returns `Task<bool>`. Return `true` to start the automation process when the trigger fires, or `false` to skip it. The default implementation always returns `true`, so the process starts whenever the trigger fires.

The `Evaluate` signature depends on the trigger's base class:

| Base class                              | Evaluate parameters                                                                                                            |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `AutomationTrigger`                     | `AutomationTriggerContext context`<br>`CancellationToken cancellationToken`                                                    |
| `AutomationTrigger<TData>`              | `AutomationTriggerContext context`<br>`TData triggerData`<br>`CancellationToken cancellationToken`                             |
| `AutomationTrigger<TData, TProperties>` | `AutomationTriggerContext context`<br>`TProperties properties`<br>`TData triggerData`<br>`CancellationToken cancellationToken` |

You can implement `Evaluate` logic based on the [trigger data](#create-custom-triggers-with-typed-data), the [properties](#create-custom-triggers-with-properties) of specific trigger instances, or other general conditions.

To access the contact the trigger was fired against, call the `GetProcessedObject` extension method (from the `CMS.ContactManagement` namespace) on `AutomationTriggerContext`. The method returns a `ContactInfo` object representing the contact.

```csharp title="Example - Conditional trigger based on contact properties"
using System.Threading;
using System.Threading.Tasks;

using CMS.Automation;
using CMS.ContactManagement;

namespace Acme.Automation;

public class KnownEmailContactTrigger : AutomationTrigger
{
    public override async Task<bool> Evaluate(AutomationTriggerContext context, CancellationToken cancellationToken)
    {
        // Gets the contact the trigger was fired against
        ContactInfo contact = await context.GetProcessedObject(cancellationToken);

        // Starts the process only for contacts that have a known email address
        return !string.IsNullOrEmpty(contact.ContactEmail);
    }
}
```

> **Note:** `Evaluate` runs in the background before the process starts. Keep it lightweight – it is called for every fired trigger.

## Register custom triggers

Register custom triggers using the `RegisterAutomationTrigger<TTrigger>` assembly attribute. We recommend placing the attribute at the top of the trigger's source file.

Specify the following parameters:

- `identifier` – a unique, stable string identifier for the trigger. We recommend using a unique prefix in your identifiers to prevent conflicts. For example, include your company's name as a prefix (`Acme.PurchaseTrigger`). Never change this value once the trigger is deployed and used in automation processes.
  > **Note:** For easy access and reuse, store the identifier value in the trigger class within an `IDENTIFIER` constant.

- `displayName` – the name displayed in the Automation Builder trigger selection dialog.

You can also set the following optional properties:

- `IconName` – the icon displayed for the trigger tile in the builder. Use icons from the set of icons starting with the _xp-_ prefix, referenced by the constants in the `Kentico.Xperience.Admin.Base.Icons` class.
- `Description` – hover text displayed as a tooltip in the Automation Builder trigger selection dialog.

```csharp title="Trigger registration example"
using CMS.Automation;
using Kentico.Xperience.Admin.Base;

[assembly: RegisterAutomationTrigger<PurchaseTrigger>(
    identifier: PurchaseTrigger.IDENTIFIER,
    displayName: "Purchase completed",
    Description = "Fires when a customer completes a purchase.",
    IconName = Icons.ShoppingCart)]
```

Once registered, the trigger appears in the trigger selection dialog when creating or editing an automation process in the Automation Builder.

> **Warning:** Deleting or unregistering a trigger that is used by an existing automation process breaks the process. If a process contains an "unknown" trigger, the Automation Builder UI will not be available to change the trigger. The process cannot be recovered until the trigger is restored and registered.

## Fire triggers from code

To fire custom triggers, inject the `IAutomationTriggerDispatcher` service and call its `FireTrigger<TTrigger>` method, where `TTrigger` is a trigger class inheriting from one of the `AutomationTrigger` base class variants.

`FireTrigger<TTrigger>` accepts an `AutomationTriggerDispatch` parameter, which describes a single trigger fire request. Construct the `AutomationTriggerDispatch` object with the following parameters:

- `ContactInfo contact` – the [contact](https://docs.kentico.com/documentation/business-users/digital-marketing/contact-management.md) for which the triggered automation processes will start.
- `IAutomationTriggerData triggerData` – use the constructor overload with this parameter for [triggers with data](#create-custom-triggers-with-typed-data). The `triggerData` instance must match the trigger's declared data type.

```csharp title="Firing a trigger with data"
using Microsoft.AspNetCore.Mvc;
using System.Threading;
using System.Threading.Tasks;

using CMS.Automation;
using CMS.ContactManagement;

using Acme.Automation;

public class CheckoutController(IAutomationTriggerDispatcher triggerDispatcher) : Controller
{
    // Creates a new order when a purchase is completed
    public async Task<IActionResult> CreateOrder(CancellationToken cancellationToken)
    {
        // ...

        // Gets the current contact
        ContactInfo currentContact = ContactManagementContext.GetCurrentContact();

        // Fires a trigger with populated trigger data
        await triggerDispatcher.FireTrigger<HighValuePurchaseTrigger>(
            new AutomationTriggerDispatch(currentContact, new PurchaseTriggerData
            {
                OrderId = "ORD-2026-00451",
                TotalAmount = 149.99m,
                Currency = "USD"
            }), cancellationToken); 

        // ...
    }
}
```

### Where to fire triggers

You can fire triggers from any code within your Xperience application. Common locations are:

- [Event handlers](https://docs.kentico.com/documentation/developers-and-admins/customization/handle-global-events.md) reacting to application-level events (e.g., contact updated)
  - See: [Example - Fire from an event handler](#example---fire-from-an-event-handler)
- Custom middleware, integration code or controllers
  - See: [Example - Fire from a checkout controller](#example---fire-from-a-checkout-controller)
- API endpoints that receive external webhooks
- [Scheduled tasks](https://docs.kentico.com/documentation/developers-and-admins/customization/scheduled-tasks.md) (regularly start a process for a set of contacts matching certain criteria)
  - See: [Time-based triggers](#time-based-triggers)

> **Tip:** **Chaining automation processes**
>
> If you wish to start an automation process from another process, we recommend that you use [custom activities](https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/set-up-activities/custom-activities.md) – log a custom activity using the built-in _Log custom activity_ step and then select the _Custom activity_ trigger for the target processes.
>
> It is possible to fire triggers from the code of [custom automation actions](https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/automation-customization/automation-custom-steps.md), but the activity approach avoids the need to develop automation components and also ensures a reliable record of the event in the [activity log](https://docs.kentico.com/documentation/business-users/digital-marketing/contact-activities.md) for the related contacts.

### Trigger firing behavior

- `FireTrigger<TTrigger>` enqueues the trigger for background processing and returns immediately without blocking your application code. Triggered automation processes then run in a background thread.
- Fired triggers are held in a bounded in-memory queue shared by all custom triggers and the built-in _Form_ trigger. If the queue becomes saturated – for example, when a large number of triggers are fired before the processes finish or reach a _Wait_ step – additional triggers are dropped and the system [logs](https://docs.kentico.com/documentation/developers-and-admins/development/logging.md) a warning. Increase the `CMSAutomationTriggerQueueCapacity` [application setting](https://docs.kentico.com/documentation/developers-and-admins/configuration/reference-configuration-keys.md) if this occurs.
- If no automation process is configured to start from the trigger, the call is a silent no-op.
- The dispatcher throws an `InvalidOperationException` when:
  - The trigger type specified in the `TTrigger` generic is not registered via `RegisterAutomationTrigger<TTrigger>`.
  - [Trigger data](#create-custom-triggers-with-typed-data) is supplied via `AutomationTriggerDispatch` for a trigger that does not declare a data type.
  - The supplied trigger data is not an instance of the trigger's declared data type.

### Example - Fire from an event handler

The following example demonstrates how to fire an automation trigger from an [object event handler](https://docs.kentico.com/documentation/developers-and-admins/customization/handle-global-events/handle-object-events.md) for contact updates.

```csharp title="Firing a trigger from a contact update handler"
using System.Threading;
using System.Threading.Tasks;

using CMS.Automation;
using CMS.ContactManagement;
using CMS.DataEngine;
using Kentico.Xperience.Admin.Base;

using Acme.Automation;

[assembly: RegisterAutomationTrigger<ContactUpdatedTrigger>(
    identifier: ContactUpdatedTrigger.IDENTIFIER,
    displayName: "Contact updated",
    Description = "Fires when an existing contact is updated.",
    IconName = Icons.UserCheckbox)]

namespace Acme.Automation;

public class ContactUpdatedTrigger : AutomationTrigger
{
    public const string IDENTIFIER = "Acme.ContactUpdatedTrigger";
}

// Event handler triggered after a contact object is updated
// Fires a custom automation trigger for the updated contact
public class ContactUpdatedTriggerHandler(IAutomationTriggerDispatcher triggerDispatcher) : IInfoObjectEventHandler<InfoObjectAfterUpdateEvent<ContactInfo>>
{
    // Invoked for synchronous contact updates (Set operation)
    public void Handle(InfoObjectAfterUpdateEvent<ContactInfo> infoObjectEvent)
    {
        ContactInfo contact = infoObjectEvent.InfoObject;

        // Fires the trigger for the updated contact (blocks on the async call)
        triggerDispatcher
            .FireTrigger<ContactUpdatedTrigger>(new AutomationTriggerDispatch(contact))
            .GetAwaiter().GetResult();
    }

    // Invoked by the asynchronous contact updates (SetAsync operation)
    public async Task HandleAsync(InfoObjectAfterUpdateEvent<ContactInfo> infoObjectEvent, CancellationToken cancellationToken)
    {
        ContactInfo contact = infoObjectEvent.InfoObject;

        // Fires the trigger for the updated contact
        await triggerDispatcher.FireTrigger<ContactUpdatedTrigger>(
            new AutomationTriggerDispatch(contact), cancellationToken);
    }
}
```

### Example - Fire from a checkout controller

The following example demonstrates how to fire an automation trigger from a controller that handles the checkout process and [order creation](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/checkout-process/order-creation.md) in a digital commerce project.

> **Info:** See [Create custom triggers with typed data](#create-custom-triggers-with-typed-data) for the code of the fired trigger and the used trigger data.

```csharp title="Firing a trigger from a controller"
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Threading;
using System.Threading.Tasks;

using CMS.Automation;
using CMS.Commerce;
using CMS.ContactManagement;
using CMS.DataEngine;

using Kentico.Membership;

using Acme.Automation;

namespace Acme.Automation;

public class CheckoutController(
    IAutomationTriggerDispatcher triggerDispatcher,
    UserManager<ApplicationUser> userManager,
    IOrderCreationService<OrderData, PriceCalculationRequest, PriceCalculationResult, AddressDto> orderCreationService,
    IInfoProvider<OrderInfo> orderInfoProvider) : Controller
{
    // Creates a new order when a purchase is completed
    public async Task<IActionResult> CreateOrder(CancellationToken cancellationToken)
    {
        // Prepare order data from your checkout form
        var orderData = new OrderData
        {
            // Identifies the authenticated user for the order, if one exists
            BuyerIdentifier = BuyerIdentifier.FromMemberId(
                (await userManager.GetUserAsync(User))?.Id ?? 0),
            OrderNumber = "ORD-2026-00451",
            LanguageName = "en",
            BillingAddress = new AddressDto
            {
                FirstName = "John",
                LastName = "Doe",
                Email = "john@example.com",
                Line1 = "123 Main St",
                City = "New York",
                Zip = "10001",
                CountryID = 1,
                StateID = 1
            },
            ShippingAddress = null,
            OrderItems = new[]
            {
                new OrderItem
                {
                    ProductIdentifier = new ProductIdentifier { Identifier = 123 },
                    Quantity = 2
                }
            }
        };

        // Creates the order
        var orderId = await orderCreationService.CreateOrder(orderData, cancellationToken);
        var order = await orderInfoProvider.GetAsync(orderId, cancellationToken);

        // Gets the current contact
        ContactInfo currentContact = ContactManagementContext.GetCurrentContact();

        // Fires a trigger with trigger data populated from the order
        await triggerDispatcher.FireTrigger<HighValuePurchaseTrigger>(
            new AutomationTriggerDispatch(currentContact, new PurchaseTriggerData
            {
                OrderId = order.OrderNumber,
                TotalAmount = order.OrderGrandTotal,
                Currency = "USD"
            }), cancellationToken); 


        // Redirects users to a confirmation page
        return RedirectToAction("Confirmation", new { orderId });
    }
} 
```

### Time-based triggers

While most automation triggers are implemented as a reaction to a specific event in your application, you can also fire triggers on a recurring schedule for a specific set of contacts. Use the following approach:

1. Create a [scheduled task](https://docs.kentico.com/documentation/developers-and-admins/customization/scheduled-tasks.md).

2. Configure the [task schedule](https://docs.kentico.com/documentation/developers-and-admins/customization/scheduled-tasks.md#configure-task-schedule) to control how often the trigger fires.

   > **Warning:** Run scheduled tasks that fire triggers **at most once per day**. More frequent intervals are not recommended. On projects with a large number of contacts, each run may enqueue a large amount of background automation work and can lead to high application load.

3. In the task's `Execute` method:
   1. Load the contacts for which you want to fire the trigger (e.g., contacts with low engagement, contacts classified as hot leads).
   2. Fire the trigger for each selected contact.

```csharp title="Example - Contact birthday trigger"
using CMS.Automation;
using CMS.ContactManagement;
using CMS.DataEngine;
using CMS.Scheduler;
using Kentico.Xperience.Admin.Base;

using System;
using System.Collections.Generic;

using System.Threading;
using System.Threading.Tasks;

using Acme.Automation;

[assembly: RegisterScheduledTask(identifier: "Acme.ContactBirthdayAutomationTask", typeof(ContactBirthdayAutomationTask))]

[assembly: RegisterAutomationTrigger<ContactBirthdayTrigger>(
    identifier: ContactBirthdayTrigger.IDENTIFIER,
    displayName: "Contact birthday",
    Description = "Fires once per day for contacts whose birthday is on the given day.",
    IconName = Icons.Gift)]

namespace Acme.Automation;

public class ContactBirthdayTrigger : AutomationTrigger
{
    public const string IDENTIFIER = "Acme.ContactBirthdayTrigger";
}

public class ContactBirthdayAutomationTask(IAutomationTriggerDispatcher triggerDispatcher,
                                           IInfoProvider<ContactInfo> contactInfoProvider) : IScheduledTask
{
    public async Task<ScheduledTaskExecutionResult> Execute(ScheduledTaskConfigurationInfo task, CancellationToken cancellationToken)
    {

        DateTime today = DateTime.Today;

        // Loads contacts whose birthday (day and month) matches today
        IEnumerable<ContactInfo> contactsWithBirthday = await contactInfoProvider.Get()
                .WhereNotNull(nameof(ContactInfo.ContactBirthday))
                .WhereEquals($"DAY([{nameof(ContactInfo.ContactBirthday)}])".AsExpression(), today.Day)
                .WhereEquals($"MONTH([{nameof(ContactInfo.ContactBirthday)}])".AsExpression(), today.Month)
                .GetEnumerableTypedResultAsync(cancellationToken: cancellationToken);
        
        // Fires the trigger for each applicable contact
        foreach (ContactInfo contact in contactsWithBirthday)
        {
            await triggerDispatcher.FireTrigger<ContactBirthdayTrigger>(
                new AutomationTriggerDispatch(contact), cancellationToken);
        }

        return ScheduledTaskExecutionResult.Success;
    }
}
```

## Best practices

Most [best practices for custom actions](https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/automation-customization/automation-custom-steps.md#best-practices) also apply to custom triggers – error handling, data protection (GDPR), property guidelines, and dependency injection.

### Keep triggers stateless and lightweight

- When a trigger is fired, the system creates and reuses a single instance of the trigger class to evaluate and start all processes with the given trigger. Do not store process-specific state in trigger class members.
- Keep any `Evaluate` override lightweight – it runs for every fired trigger before the process starts. Only retrieve data required for the evaluation conditions and avoid any operations that modify or create data.

### Trigger evaluation timeout

Custom triggers have a maximum [evaluation](#control-process-start-with-evaluate) time of **2 minutes**. If a trigger does not evaluate within this limit, it will **not start** (equivalent to a `false` result), and the system activates the `CancellationToken` passed to the `Evaluate` method.

### Trigger data guidelines

- Keep data payloads small – include only identifiers and values that downstream actions need.
- Provide a stable, unique `Identifier` on your `IAutomationTriggerData` types and never change it.
- Use the [init accessor](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init) for trigger data properties to ensure immutability.
- All `DateTime` values are serialized and deserialized as UTC.
- Avoid storing personal or sensitive data (names, emails, API keys) directly in trigger data – store identifiers and look up details in downstream actions.
- If trigger data cannot be deserialized (e.g., after a schema change or after the trigger is unregistered), downstream actions reading it receive `null`.

### Updating and versioning triggers

- Never change the `identifier` of a registered trigger or trigger data class – doing so breaks all processes that reference it.
- Adding new optional [properties](#create-custom-triggers-with-properties) to a trigger data class is safe – they receive default values for existing trigger configurations.
- Removing or renaming properties in trigger data breaks deserialization for processes started before the change.
  - If you need to remove or replace a property without breaking existing configurations, you can remove the property's UI form component attribute to hide it in the configuration dialog.

To version a trigger safely:

1. Create a new trigger class with a new identifier (e.g., with a `_V2` suffix), along with a new data and properties class if required.
2. Keep the old version registered and functional.
3. Instruct marketers to only use the new trigger version, for example by updating the old version's display name and description.
4. Once all processes using the old trigger are disabled or deleted, the old trigger class can be removed.
