Skip to main content

VRL script

What is VRL?

VRL (Vector Remap Language) is a powerful, safe, and expressive scripting language for transforming structured data. It is developed and maintained by the Vector project, with comprehensive documentation available at the official VRL reference.

How RustMailer Uses VRL

In RustMailer, VRL scripts are used in Webhook and NATS hook pipelines to:

  • Filter incoming event payloads
  • Transform and reshape event structures
  • Reformat data to match external service schemas
  • Route only relevant events to the correct destination

This makes it possible to adapt RustMailer’s flexible event system to your own infrastructure or third-party tools with minimal configuration.

Learn More

VRL Debugging Tool

We provide an integrated VRL debugging tool in the RustMailer Web UI, similar to https://playground.vrl.dev/. This tool includes example payloads for various event types as input, allowing you to:

  • Edit and test your VRL scripts directly on the page
  • See immediate output results
  • Iterate until your script behaves as expected

Alternatively, you can use the official VRL Playground by copying the example events there. VRL offers clear syntax error messages to help you debug and correct scripts efficiently.

vrlScreenshot

Example: Filter and Transform EmailAddedToFolder Event

if .event_type == "EmailAddedToFolder" {
# Create a new object to store filtered and transformed data
my_payload = {}

# Copy event type
my_payload.event_type = .event_type

# Format the timestamp from milliseconds Unix time to "YYYY-MM-DD HH:MM:SS"
my_payload.timestamp = format_timestamp!(
from_unix_timestamp!(.timestamp, unit:"milliseconds"), format:"%Y-%m-%d %H:%M:%S"
)

# Extract email subject and sender address
my_payload.subject = .payload.subject
my_payload.from = .payload.from.address

# Include mailbox name and email size
my_payload.mailbox = .payload.mailbox_name
my_payload.size = .payload.size

# Map the recipients array to extract only their addresses
my_payload.to = map_values(array!(.payload.to)) -> |recipient| {recipient.address}

# Check if there are any CC addresses and extract them
if .payload.cc != null {
my_payload.cc = map_values(array!(.payload.cc)) -> |cc| {cc.address}
}

# If attachments exist, map to a simplified list containing filename, type, and size
if length(array!(.payload.attachments)) > 0 {
my_payload.attachments = map_values(array!(.payload.attachments)) -> |attachment| {
{
"filename": attachment.filename,
"type": attachment.file_type,
"size": attachment.size
}
}
}

# Extract plain text content if available; otherwise extract HTML content, truncating to 200 chars
if .payload.message.plain.content != null {
my_payload.content = truncate!(.payload.message.plain.content, 200)
} else if .payload.message.html != null {
my_payload.content = truncate!(.payload.message.html, 200)
}

# Replace the original event with the filtered payload
. = my_payload
} else {
# For all other event types, discard the event by setting output to null
. = null
}
tip

This script filters incoming events, only processes those where event_type equals "EmailAddedToFolder", and reshapes the payload to include only the relevant fields. All other events are discarded by returning null.

Key Concepts

  • .event_type — The leading dot . represents the entire input JSON object for the current event.
  • You can access nested fields with dot notation, e.g. .payload.subject or .payload.from.address.
  • Setting . = null discards the event (filters it out).

VRL provides many built-in functions to manipulate data, such as:

  • truncate!(string, length) — Truncate a string to a maximum length.
  • array!(value) — Convert a value into an array (if not already).
  • map_values(array) -> |x| { ... } — Transform each element in an array.
  • format_timestamp!(timestamp, format: "...") — Format timestamps to strings.
  • del(.field) — Delete a field from the object.
  • now() — Return the current timestamp.
  • parse_int!(string) — Parse a string into an integer.
info

For a full list, visit the official VRL function reference:
https://vector.dev/docs/reference/vrl/functions/

Functions with a ! at the end (like truncate!, parse_int!, from_unix_timestamp!) follow a Rust-style macro naming convention.
They are fallible functions — if an error occurs, they will fail explicitly rather than silently.
Don't be intimidated — this helps you catch data issues early and reliably.


Common VRL Usage Examples

# Remove a field named "foo"
del(.foo)

# Add a current timestamp field
.timestamp = now()

# Parse HTTP status code and remove the original field
http_status_code = parse_int!(.http_status)
del(.http_status)

# Add a new "status" field based on HTTP status code
if http_status_code >= 200 && http_status_code <= 299 {
.status = "success"
} else {
.status = "error"
}

Why VRL?

VRL is purpose-built for structured data transformation. It’s designed to be:

  • Safe — No panics or crashes, errors must be handled explicitly.
  • Fast — Optimized to run at high throughput in streaming environments.
  • Powerful — Packed with built-in functions for working with strings, timestamps, arrays, objects, and more.
Built for data processing

VRL is designed specifically to solve data problems. Whether you're reshaping complex payloads, filtering noisy events, or masking sensitive information, VRL gives you expressive, powerful tools to do it safely and efficiently.

You don’t need to write full programs — just short expressions or small blocks that manipulate your JSON-like event structure.
With functions like truncate!, format_timestamp!, array!, map_values, and many others, you can accomplish most tasks without external tools or services.

You can explore all available functions in the official documentation:
👉 https://vector.dev/docs/reference/vrl/functions/

Important Limitations

Each account in RustMailer can configure only one hook, and each hook is limited to a single VRL script.

This means:

  • Your script must handle all relevant event types (e.g., EmailFlagsChanged, EmailBounce, EmailAddedToFolder, etc.) within a single logic block.
  • You cannot define separate VRL scripts for different event types within the same hook.

However:

  • RustMailer also supports global hooks, which apply to events across all accounts.
  • Each global hook can also have its own VRL script.

Design your script accordingly to detect and handle different event types inside the same block using conditional logic (e.g., if .event_type == "..." { ... } else { ... }).

Good to Know

The good news is: most RustMailer event types have very simple structures — typically just an event_type, timestamp, and basic metadata.

The only exception is the EmailAddedToFolder event, which contains detailed email content such as subject, sender, recipients, attachments, and message body.

This makes it easy to write concise and maintainable VRL scripts for most cases, and only extend your logic when handling rich-content events like new emails.