# Time to Live

User requests an OTP. Your queue is backed up; the SMS worker is 8 minutes behind. By the time the message is handled, the code is invalid and you spam the user with a useless code. **Time to Live** drops the message if it sits in the queue longer than its lifetime — useful for OTPs, real-time notifications, cache-warm requests, and any message whose value expires before delivery.

## Message Handler Time to Live

You may delay handling given asynchronous message by adding `#[Delayed]` attribute.

```php
#[TimeToLive(new TimeSpan(seconds: 50))]
#[Asynchronous("notifications")]
#[EventHandler(endpointId: "welcomeEmail")]
public function sendWelcomeNotificationWhen(UserWasRegistered $event): void
{
   // handle welcome notification
}
```

### Using Expression language

To dynamically calculate expected TTL, we can use expression language.

```php
#[TimeToLive(expression: 'headers["expirationTime"]']
#[Asynchronous("notifications")]
#[EventHandler(endpointId: "welcomeEmail")]
public function sendWelcomeNotificationWhen(UserWasRegistered $event): void
{
   // handle welcome notification
}
```

{% hint style="success" %}
**payload** variable in expression language will hold **Command/Event object.**\
**headers** variable will hold all related **Mesage Headers**.
{% endhint %}

We could also **access any object from our Dependency Container,** in order to calculate the delay and pass there our **Command**:

```php
#[Delayed(expression: 'reference("delayService").calculate(payload)']
#[Asynchronous("notifications")]
#[EventHandler(endpointId: "welcomeEmail")]
public function sendWelcomeNotificationWhen(UserWasRegistered $event): void
{
   
}
```

## Message Time to Live

We may send an Message and tell Ecotone to set Time to Live using **timeToLive** Message Header:

```php
$commandBus->sendWithRouting(
    "sendOneTimePassword",
    "userId",
    metadata: ["timeToLive" => new TimeSpan(minutes: 5)]
);
```

{% hint style="info" %}
[Asynchronous Message Channel ](/modelling/asynchronous-handling.md)need to support this option to be used
{% endhint %}

## Controlling Header Override Behavior

By default, the `#[TimeToLive]` attribute will **override** any existing `timeToLive` header that was set when sending the message. However, you can change this behavior using the `shouldReplaceExistingHeader` parameter.

### Using Attribute as Default (Not Override)

When you want the attribute to act as a **default value** that can be overridden by message metadata:

```php
#[TimeToLive(new TimeSpan(minutes: 5), shouldReplaceExistingHeader: false)]
#[Asynchronous("notifications")]
#[EventHandler(endpointId: "sendOTP")]
public function sendOneTimePassword(OTPRequested $event): void
{
   // handle OTP sending
}
```

With `shouldReplaceExistingHeader: false`:

* If the message **already has** a `timeToLive` header, the attribute will **not** override it
* If the message **does not have** a `timeToLive` header, the attribute value will be used

This is useful when you want:

* Handler-level defaults via attributes
* Per-message overrides via metadata (e.g., different TTL based on message priority or type)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ecotone.tech/modelling/asynchronous-handling/time-to-live.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
