# Halon manual
Version: 6.12-stable
[Halon](http://halon.io/) Engage & Protect is a fast, flexible and powerful email infrastructure software (MTA);
designed for demanding uses such as large-scale email services. The software is provided as Linux binaries, tooling for configuration and deployment, and management interfaces
for maximum adaptability to specific uses.
This manual references many other resources, such as:
* [Script language reference](https://docs.halon.io/hsl/)
* [Script library](https://docs.halon.io/go/scriptlibrary)
* [Knowledge base](https://docs.halon.io/go/knowledgebase)
## Table of contents
* [System requirements](#requirements)
* [Storage disks](#requirements:storage-disks)
* [CPU](#requirements:cpu)
* [RAM](#requirements:ram)
* [DNS](#requirements:dns)
* [Linux tunables](#requirements:linux-tunables)
* [Installation](#install)
* [Linux](#install:linux)
* [Ubuntu](#install:ubuntu)
* [Version pinning](#install:version-pinning)
* [RHEL](#install:rhel)
* [Version locking](#install:version-locking)
* [Azure Linux](#install:azure-linux)
* [Docker & K8s](#install:docker-k8s)
* [Upgrade](#install:upgrade)
* [Versioning scheme](#install:versioning-scheme)
* [Lifecycle compatibility](#install:lifecycle-compatibility)
* [License](#install:license)
* [Configuration](#install:configuration)
* [Programs](#install:programs)
* [Default paths](#install:default-paths)
* [File permissions](#install:file-permissions)
* [Troubleshooting](#install:troubleshooting)
* [Configuration](#config)
* [Startup configuration](#config_startup)
* [Server directives](#config_startup:server-directives)
* [Monitor directives](#config_startup:monitor-directives)
* [Queue directives](#config_startup:queue-directives)
* [Threads directives](#config_startup:threads-directives)
* [Scripts](#config_startup:scripts)
* [Events](#config_startup:events)
* [Other directives](#config_startup:other-directives)
* [Clustering directives](#config_startup:clustering-directives)
* [Environment directives](#config_startup:environment-directives)
* [Performance and log](#config_startup:performance-and-log)
* [Configuration paths](#config_startup:configuration-paths)
* [Other environment](#config_startup:other-environment)
* [Running configuration](#config_smtpd)
* [Script directives](#config_smtpd:script-directives)
* [Server directives](#config_smtpd:server-directives)
* [Transport directives](#config_smtpd:transport-directives)
* [Monitor directives](#config_smtpd:monitor-directives)
* [Queue directives](#config_smtpd:queue-directives)
* [General directives](#config_smtpd:general-directives)
* [List directives](#config_smtpd:list-directives)
* [Resolver directives](#config_smtpd:resolver-directives)
* [Other directives](#config_smtpd:other-directives)
* [Example](#config_smtpd:example)
* [Suspend configuration](#config_suspend)
* [Suspend directives](#config_suspend:suspend-directives)
* [Policy configuration](#config_policy)
* [Policy directives](#config_policy:policy-directives)
* [Rate directives](#config_policy:rate-directives)
* [Delivery configuration](#config_delivery)
* [Delivery directives](#config_delivery:delivery-directives)
* [Performance tuning](#config_tuning)
* [Thread tuning](#config_tuning:thread-tuning)
* [servers[].threads.event](#config_tuning:servers-threads-event)
* [threads.scripts[].count](#config_tuning:threads-scripts-count)
* [threads.scripts[].concurrent](#config_tuning:threads-scripts-concurrent)
* [queues.threads.pickup](#config_tuning:queues-threads-pickup)
* [resolver.threads.event](#config_tuning:resolver-threads-event)
* [resolver.threads.ready](#config_tuning:resolver-threads-ready)
* [Startup configuration](#config_tuning:startup-configuration)
* [environment.syslog.mask](#config_tuning:environment-syslog-mask)
* [spool.fsync](#config_tuning:spool-fsync)
* [spool.loader.threads.wait](#config_tuning:spool-loader-threads-wait)
* [environment.uuid.version](#config_tuning:environment-uuid-version)
* [Running configuration](#config_tuning:running-configuration)
* [servers[].logging](#config_tuning:servers-logging)
* [servers[].phases.connect.remoteptr](#config_tuning:servers-phases-connect-remoteptr)
* [servers[].phases.data.multipart](#config_tuning:servers-phases-data-multipart)
* [servers[].phases.data.fixheaders](#config_tuning:servers-phases-data-fixheaders)
* [Resolver subsystem](#config_tuning:resolver-subsystem)
* [resolver.concurrency](#config_tuning:resolver-concurrency)
* [resolver.ednsbuffersize](#config_tuning:resolver-ednsbuffersize)
* [resolv.conf](#config_tuning:resolv-conf)
* [systemd](#config_tuning:systemd)
* [K8s](#config_tuning:k8s)
* [Queueing subsystem](#config_tuning:queueing-subsystem)
* [queues.pickup.prereaddata](#config_tuning:queues-pickup-prereaddata)
* [queues.pickup.backpressure](#config_tuning:queues-pickup-backpressure)
* [pooling](#config_tuning:pooling)
* [Memory tuning](#config_tuning:memory-tuning)
* [Disk tuning](#config_tuning:disk-tuning)
* [Queuing subsystem](#queue)
* [Active queue](#queue_active)
* [Active queue pickup](#queue_active:active-queue-pickup)
* [Delivery settings](#queue_delivery)
* [Queue policies](#queue_policy)
* [Available fields](#queue_policy:available-fields)
* [Loading policy](#queue_policy:loading-policy)
* [Policy counter thresholds](#queue_policy:policy-counter-thresholds)
* [Policy conditions](#queue_policy:policy-conditions)
* [Policy properties](#queue_policy:policy-properties)
* [Backoff policy](#queue_policy:backoff-policy)
* [Policies with *uniqueip*](#queue_policy:policies-with-uniqueip)
* [Policy precedence](#queue_policy:policy-precedence)
* [Policy counter groups](#queue_policy:policy-counter-groups)
* [Cluster-aware policy](#queue_cluster)
* [Example architecture](#queue_cluster:example-architecture)
* [Delivery Orchestrator: `clusterd`](#queue_cluster:delivery-orchestrator-clusterd)
* [Writing policy for clusters](#queue_cluster:writing-policy-for-clusters)
* [Advanced usage: selective non-clustered properties](#queue_cluster:advanced-usage-selective-non-clustered-properties)
* [Observing clustered policy in operation](#queue_cluster:observing-clustered-policy-in-operation)
* [Moving messages between instances](#queue_cluster:moving-messages-between-instances)
* [Message suspension](#queue_suspend)
* [Excluding IPs from pool](#queue_suspend:excluding-ips-from-pool)
* [Changing message states](#queue_changing)
* [Messages on hold](#queue_changing:messages-on-hold)
* [Spool startup](#queue_load)
* [On-disk format](#queue_load:on-disk-format)
* [Queue statistics via CLI/API](#queue_cli)
* [Command line interface](#cli)
* [Queue management](#cli_queue)
* [List messages](#cli_queue:list-messages)
* [Export messages](#cli_queue:export-messages)
* [Take actions on messages](#cli_queue:take-actions-on-messages)
* [Actions and working states](#cli_queue:actions-and-working-states)
* [Show messages distribution](#cli_queue:show-messages-distribution)
* [Show message structure in queue](#cli_queue:show-message-structure-in-queue)
* [Active queue policies](#cli_queue:active-queue-policies)
* [Active queue suspensions](#cli_queue:active-queue-suspensions)
* [Active queue delivery settings](#cli_queue:active-queue-delivery-settings)
* [Moving the queue files with *hqfmove*](#cli_queue:moving-the-queue-files-with-hqfmove)
* [Trace messages](#cli_queue:trace-messages)
* [Loading queue files](#cli_queue:loading-queue-files)
* [Configuration management](#cli_config)
* [Script syntax and packing](#cli_config:script-syntax-and-packing)
* [Reloading configurations](#cli_config:reloading-configurations)
* [Blue-green testing](#cli_config:blue-green-testing)
* [Remote configuration](#cli_config:remote-configuration)
* [Script engine](#cli_script)
* [Shared memory functions](#cli_script:shared-memory-functions)
* [Cache statement](#cli_script:cache-statement)
* [DNS resolver](#cli_dns)
* [Statistics and information](#cli_stats)
* [halontop](#cli_stats:halontop)
* [halonctl process-stats](#cli_stats:halonctl-process-stats)
* [Metrics summary](#openmetrics)
* [Script interpreter](#cli_hsh)
* [Options](#cli_hsh:options)
* [Script interpreter](#cli_hsh:id1)
* [Interactive shell](#cli_hsh:interactive-shell)
* [API](#api)
* [Endpoint](#api_endpoints)
* [Monitor](#api_monitor)
* [Endpoints](#api_monitor:endpoints)
* [/metrics](#api_monitor:metrics)
* [/ready](#api_monitor:ready)
* [/healthy](#api_monitor:healthy)
* [Control socket](#api_control_sockets)
* [HTTP/JSON](#api_http_json)
* [Installation](#api_install)
* [Distributions](#api_install:distributions)
* [Ubuntu](#api_install:ubuntu)
* [RHEL](#api_install:rhel)
* [Azure Linux](#api_install:azure-linux)
* [Startup](#api_install:startup)
* [Upgrade](#api_upgrade)
* [Configuration](#api_config)
* [Authentication directives](#api_config:authentication-directives)
* [Listener directives](#api_config:listener-directives)
* [Other directives](#api_config:other-directives)
* [Environment directives](#api_config:environment-directives)
* [Examples](#api_examples)
* [API reference](#api_reference)
* [Command list](#api_reference:command-list)
* [Version compatibility](#api_reference:version-compatibility)
* [HTTP submission](#http_submission)
* [Endpoint reference](#http_submission:endpoint-reference)
* [Authentication](#http_submission:authentication)
* [HTTP request format](#http_submission:http-request-format)
* [HTTP response format](#http_submission:http-response-format)
* [Examples](#http_submission:examples)
* [Web administration](#web)
* [Code editor integrations](#editors)
* [Visual Studio Code](#vscode)
* [Extensions](#vscode:extensions)
* [Configuration Packer](#vscode:configuration-packer)
* [HSL Syntax](#vscode:hsl-syntax)
* [HSL Linter](#vscode:hsl-linter)
* [HSL Debugger](#vscode:hsl-debugger)
* [Code Companion](#vscode:code-companion)
* [Providing feedback](#vscode:providing-feedback)
* [Working with Dev Containers and Remote SSH](#vscode:working-with-dev-containers-and-remote-ssh)
* [Limitations](#vscode:limitations)
* [Copilot error messages](#vscode:copilot-error-messages)
* [Setting up a development environment](#vscode:setting-up-a-development-environment)
* [Editing YAML with custom tags](#vscode:editing-yaml-with-custom-tags)
* [Vim](#vim)
* [Features](#vim:features)
* [Installation](#vim:installation)
* [Upgrade](#vim:upgrade)
* [Screenshots](#vim:screenshots)
* [Plugins](#plugins)
* [FFI](#plugins_ffi)
* [Native plugins](#plugins_native)
* [Exported by plugin](#plugins_native:exported-by-plugin)
* [Command plugin](#plugins_native:command-plugin)
* [HSL plugin](#plugins_native:hsl-plugin)
* [Queue plugin](#plugins_native:queue-plugin)
* [Delivery plugin](#plugins_native:delivery-plugin)
* [Exported by MTA](#plugins_native:exported-by-mta)
* [Objects](#plugins_native:objects)
* [Callable](#plugins_native:callable)
* [HSL plugin](#plugins_native:id1)
* [Lists plugin](#plugins_native:lists-plugin)
* [Inject plugin](#plugins_native:inject-plugin)
* [Import plugin](#plugins_native:import-plugin)
* [Queue plugin](#plugins_native:id4)
* [Delivery plugin](#plugins_native:id5)
* [Monitor plugin](#plugins_native:monitor-plugin)
* [HSL plugins](#plugins_hsl)
* [Development](#plugins_development)
* [hsh](#plugins_development:hsh)
* [hsh.yaml](#plugins_development:hsh-yaml)
* [hsh-app.yaml](#plugins_development:hsh-app-yaml)
* [Examples](#plugins_examples)
* [Changelog](#changelog)
# System requirements
This section describes hardware requirements and software tuning for correct and performant operations.
Most of these recommendations are not specific to Halon, but rather any disk intensive (IOPS) transactional workload.
## Storage disks
MTAs are usually operated in queueing mode, especially those handling delivery out on the Internet.
Each message is first received and accepted by the MTA, and then passed on to another MTA.
Each message needs to be stored on disk, in order to guarantee that no message is lost.
For most queuing MTAs, the disk will be the bottleneck.
Both total number of IOPS (with fsync) and latency need to be considered, for a certain message size and concurrency.
The queue storage is referred to as “spool” from hereon. Consider the following:
* Spool disks should be optimized for high sustained IOPS and low latency, with regards to the expected concurrency and message size. Modern SSD/NVMe and SAS disks are known to provide high performance.
* The MTA must fsync/journal messages to disk to provide reliable deliveries and no data loss. This comes with a latency cost that needs to be considered. There is an option to disable fsync ([`spool.fsync`](#config_startup:confval-spool.fsync)), but it’s not recommended for normal use.
* RAID10 is [generally regarded](https://en.wikipedia.org/wiki/Nested_RAID_levels#RAID_10_(RAID_1+0)) as being the most performant RAID level
* Spool disks can be benchmarked with regards to IOPS, and we recommend dimensioning for a few IOPS per message/s.
* The spool file system should be mounted with *noatime* so that access times on files are not updated (the timestamp is not used).
* If possible, use a separate disk for the spool, logs and swap for better and more predictable performance.
## CPU
CPU is often the second most important resource. Network transmission is arguably more important, but it typically depends on factors you cannot control nor predict.
The MTA is event-based (asynchronous I/O) running on several event loops (multithreading).
Hence, more CPU cores and a higher clock speed will improve the overall performance.
## RAM
The MTA will trade consumed RAM for performance. Therefore, running the MTA with limited RAM is often a bad idea.
The entire queue’s metadata is loaded into memory by the MTA to allow for [virtual sub-queues](#queue_active:queue-active-queue-pickup).
This data will consume a few kb of RAM memory for each message in the different [queue states](#queue:queue-states).
## DNS
Not only MX lookup during SMTP delivery produce DNS queries,
but many of the current email technologies also based on DNS
(such as DANE, SPF, DKIM, DMARC, ARC, RBL, PTR records, etc).
Having a fast, local and caching DNS infrastructure (preferably shared among all MTAs) may improve performance and reduce latency dramatically.
The MTA itself has an internal DNS cache ([`resolver.cache.size`](#config_smtpd:confval-resolver.cache.size)) and a domain cache ([`resolver.domain.cache.size`](#config_smtpd:confval-resolver.domain.cache.size)) to relieve some stress on the local resolver as well.
> **Warning**
>
> All nameservers defined in your `/etc/resolv.conf` need to be able to answer to **all** queries and **zones**. You can not have servers which are only responsible for certain zones (eg. `.local`).
## Linux tunables
There are some Linux kernel tunables that you might want to consider.
For the most part, the default Linux tunable works just fine.
However, some are known to cause issues with the workload of a highly concurrent MTA:
* File descriptors limits, see [`environment.rlimit.nofile`](#config_startup:confval-environment.rlimit.nofile) for more information
* Port reuse and port ranges may need to be adjusted
* Memory usage, see [memory tuning](#config_tuning:malloc) for more information
# Installation
## Linux
The programs are installed using a Linux distribution package manager
according to the corresponding instructions, or by manually copying the files to the
`/opt/halon` directory.
In the examples below with `#` prompts, steps will require elevated (root) privilege, e.g. `sudo su -` before running them.
In preparation; if you have another MTA running on the same host (eg. Exim, Sendmail or Postfix), you should
in most installations stop it. To stop Postfix for example:
```console
# systemctl disable postfix
# systemctl stop postfix
```
> **Note**
>
> Some of the software provided from Halon Security AB are subject to export control restrictions listed in category 5D002
> of the Annex I of the European Union dual use regulations, Regulation (EU) 2021/821.
> The software downloaded may not be re-exported or re-transferred without the
> expressed approval from concerned authority or legislation.
### Ubuntu
To install the .deb package use the following commands, replacing the username and password with the provided credentials.
```console
# HALON_REPO_USER=
# HALON_REPO_PASS=
# wget -O- https://raw.githubusercontent.com/halon/pgp-keys/master/pgp-keys/7F0A73B5.gpg > /usr/share/keyrings/halon-archive-keyring.gpg
# . /etc/lsb-release && echo "deb [arch=`dpkg --print-architecture` signed-by=/usr/share/keyrings/halon-archive-keyring.gpg] https://repo.halon.io/ $DISTRIB_CODENAME stable" > /etc/apt/sources.list.d/halon.list
# echo "machine repo.halon.io login $HALON_REPO_USER password $HALON_REPO_PASS" > /etc/apt/auth.conf
# apt-get update
# apt-get install halon-smtpd
# halonctl license fetch --username $HALON_REPO_USER --password $HALON_REPO_PASS
# echo "$(($RANDOM % 60)) $(($RANDOM % 24)) * * * root /opt/halon/bin/halonctl license fetch --username $HALON_REPO_USER --password $HALON_REPO_PASS --syslog" > /etc/cron.d/halon-license
```
> **Note**
>
> If using a Ubuntu version older than *26.04* install the *halon* package instead of *halon-smtpd*.
#### Version pinning
To pin the .deb package to a specific minor version, use the following commands
```console
# cat <<'EOF' > /etc/apt/preferences.d/halon-smtpd
Package: halon-smtpd
Pin: version 1:6.12.*
Pin-Priority: 1000
EOF
# apt-get update
```
Now any future `apt-get upgrade` will not upgrade the package beyond the 6.12 minor version.
### RHEL
To install the .rpm package use the following commands, replacing the username and password with the provided credentials.
> **Note**
>
> The RHEL repository can also be used when installing Halon on Rocky Linux or AlmaLinux.
```console
# HALON_REPO_USER=
# HALON_REPO_PASS=
# dnf install epel-release
# REPO="/etc/yum.repos.d/Halon.repo"
# DIST="rhel$(cat /etc/redhat-release | sed 's/.*release \([0-9]\+\).*/\1/')"
# echo "[halon]" > $REPO
# echo "name=Halon repository" >> $REPO
# echo "baseurl=https://$HALON_REPO_USER:$HALON_REPO_PASS@repo.halon.io/dists/$DIST/amd64/stable" >> $REPO
# echo "gpgcheck=1" >> $REPO
# echo "gpgkey=https://raw.githubusercontent.com/halon/pgp-keys/master/pgp-keys/7F0A73B5.asc" >> $REPO
# echo "enabled=1" >> $REPO
# dnf install halon-smtpd
# halonctl license fetch --username $HALON_REPO_USER --password $HALON_REPO_PASS
# echo "$(($RANDOM % 60)) $(($RANDOM % 24)) * * * root /opt/halon/bin/halonctl license fetch --username $HALON_REPO_USER --password $HALON_REPO_PASS --syslog" > /etc/cron.d/halon-license
```
> **Note**
>
> If using a RHEL version older than *10* install the *halon* package instead of *halon-smtpd*.
The default configuration allows users in the *staff* group to manage the MTA
```console
# groupadd staff
```
#### Version locking
To lock the .rpm package to a specific version, use the following commands
```console
# yum install python3-dnf-plugin-versionlock
# yum versionlock add halon-smtpd
```
Now any future `yum update` or `yum upgrade` will not upgrade the package.
### Azure Linux
To install the .rpm package use the following commands, replacing the username and password with the provided credentials.
```console
# HALON_REPO_USER=
# HALON_REPO_PASS=
# tdnf install -y ca-certificates azurelinux-repos-extended
# REPO="/etc/yum.repos.d/Halon.repo"
# DIST="azure3"
# echo "[halon]" > $REPO
# echo "name=Halon repository" >> $REPO
# echo "baseurl=https://$HALON_REPO_USER:$HALON_REPO_PASS@repo.halon.io/dists/$DIST/amd64/stable" >> $REPO
# echo "gpgcheck=1" >> $REPO
# echo "gpgkey=https://raw.githubusercontent.com/halon/pgp-keys/master/pgp-keys/7F0A73B5.asc" >> $REPO
# echo "enabled=1" >> $REPO
# tdnf install -y halon
# halonctl license fetch --username $HALON_REPO_USER --password $HALON_REPO_PASS
# echo "$(($RANDOM % 60)) $(($RANDOM % 24)) * * * root /opt/halon/bin/halonctl license fetch --username $HALON_REPO_USER --password $HALON_REPO_PASS --syslog" > /etc/cron.d/halon-license
```
The default configuration allows users in the *staff* group to manage the MTA
```console
# groupadd staff
```
## Docker & K8s
For instructions on how to build container images of the programs as well as deploying them on K8s (Kubernetes), see our [halon-docker](https://github.com/halon/halon-docker) GitHub repository.
## Upgrade
In order to upgrade the package, install the new package over the previous installed one (see the installation chapter).
After the upgrade the service will be stopped and requires to be manually started.
Important upgrades notes will be published in the release notes documents. Those may include
* Configuration version compatibility
* Protobuf API compatibility
* C plugin API compatibility
### Versioning scheme
All packages provided both the base software packages and halon-extras plugins follow the standard format of [semantic versioning](https://semver.org/) often referred to as “SemVer”. This allows you to do version pinning on both `major.*` and `major.minor.*` releases to safely move between feature and patch releases.
### Lifecycle compatibility
The lifecycle of our software releases will follow the lifecycle of the currently supported Linux distributions.
For this reason we choose support the LTS (Long Term Support) versions of a specific Linux distribution if possible.
For an additional yearly fee the support term may be [extended](mailto:sales@halon.io?subject=Extended%20Lifecycle%20Support) during the Linux distribution extended paid support term.
| Distribution | End-of-Life | Final supported version |
|----------------|---------------|---------------------------|
| RHEL 10.x | — 2035 | |
| RHEL 9.x | — 2032 | |
| RHEL 8.x | — 2029 | |
| RHEL 7.x | Jun 2024 | 6.10.7 |
| Azure Linux 3 | — 2027 | |
| Ubuntu 26.04 | Apr 2031 | |
| Ubuntu 24.04 | Apr 2029 | |
| Ubuntu 22.04 | Apr 2027 | |
| Ubuntu 20.04 | Apr 2025 | 6.6.3 |
| Ubuntu 18.04 | Oct 2023 | 6.0.2 |
## License
In order for Halon to operate it needs to have a valid non-expired license [`environment.licensekey`](#config_startup:confval-environment.licensekey).
This license can be obtained once with the following command.
```console
# halonctl license fetch --username $HALON_REPO_USER --password $HALON_REPO_PASS
```
Importantly, the license needs to be updated before the end of the license period as the system will not operate without a valid non-expired license.
It can easily be done using eg. a *cron* job. This fetches your license once per day at a random minute and hour.
```console
# echo "$(($RANDOM % 60)) $(($RANDOM % 24)) * * * root /opt/halon/bin/halonctl license fetch --username $HALON_REPO_USER --password $HALON_REPO_PASS --syslog" > /etc/cron.d/halon-license
```
If running in a container or if you have other ways of deploying Halon where you don’t have *cron* available, another method needs to be used.
The current license status and expiration can be shown either using the `halonctl license show` or `halonctl process-stats` command or using the Web UI.
## Configuration
Each program has a small [startup configuration](#config_startup),
which contains settings such as
[control socket](#api_control_sockets) paths and user privilege drop. Most importantly,
they point at the [running configuration](#config_smtpd) load path via [`environment.appconf`](#config_startup:confval-environment.appconf).
There are multiple sample configuration files in */opt/halon/share/examples* directory.
The [running configuration](#config_smtpd) contains the bulk of the configuration data,
including all the Halon [script](https://docs.halon.io/hsl/).
The following sample configuration are available.
* */opt/halon/share/examples/minimal*
* listen on localhost, port 25
* deliver messages by lookup mx
* contains only necessary configuration
To quickly get started, follow the steps below.
```console
$ mkdir -p test/dist
$ cd test
$ cp -r /opt/halon/share/examples/minimal src
$ halonconfig
# cp dist/* /etc/halon/
```
* A sample configuration in */opt/halon/share/examples/minimal* is copied to a working directory
* The `halonconfig` script prepares the configuration files
* It validates the configuration files in *src/config* against the [JSON schemas](https://docs.halon.io/json-schemas/6.12-stable/)
* It “packs” the [script](https://docs.halon.io/hsl/) in *src/hooks* and *src/files* into the *smtpd-app.yaml* [running configuration](#config_smtpd)
* It writes the result to the *dist* directory
* The prepared configuration is copied from *dist/* to */etc/halon/*
When the initial configuration is in place, the program can be enabled on system boot and started with:
```console
# systemctl enable halon-smtpd.service
# systemctl start halon-smtpd
```
The default minimal configuration allows you to relay email on port 25 from localhost,
so you can send a test email using for example *swaks*:
```console
$ swaks --server 127.0.0.1 --port 25 --to youremail@example.com
```
## Programs
This section describes the programs. These are:
* *smtpd* which is main MTA server process, and what runs the
connect, HELO, AUTH, MAIL, RCPT, EOD, disconnect, pre- and post delivery,
and proxy [script](https://docs.halon.io/hsl/) hooks.
* *hsh* that can be used to run [standard library](https://docs.halon.io/hsl/functions.html) functions.
* *hqfeml* is used to convert `.hqf` files to `.eml`.
* *hqfcat* is used to impect `.hqf`.
* *halonctl* is used to control the MTA.
* *halontop* is used to view statistics from the MTA.
* *halonconfig* is used to verify and compile the configuration.
### Default paths
Each of the programs have default paths for configurations, Unix domain sockets, temporary directories, and
the queue. Those are specified in the
[JSON schemas](https://docs.halon.io/json-schemas/6.12-stable/), but repeated below for your convenience.
*smtpd* loads its startup configuration from */etc/halon/smtpd.yaml*, which have the following defaults:
| Setting | Default | Description |
|------------------------------------------------------------------------------------------------------------------------|--------------------------------|----------------------------------------------------------------------------------------------|
| [`environment.appconf`](#config_startup:confval-environment.appconf) | /etc/halon/smtpd-app.yaml | [Running configuration](#config_smtpd) |
| [`environment.policyconf`](#config_startup:confval-environment.policyconf) | /etc/halon/smtpd-policy.yaml | [Active queue policies](#queue_policy:queue-policy) |
| [`environment.suspendconf`](#config_startup:confval-environment.suspendconf) | /etc/halon/smtpd-suspend.yaml | [Active queue suspends](#queue_suspend:queue-suspend) |
| [`environment.deliveryconf`](#config_startup:confval-environment.deliveryconf) | /etc/halon/smtpd-delivery.yaml | [Active queue delivery settings](#queue_delivery:queue-delivery) |
| `environment.spool.path` | /var/spool/halon/queue | Queued messages |
| [`environment.publicsuffix`](#config_startup:confval-environment.publicsuffix) | | Used by DMARC functions |
| [`environment.controlsocket.path`](#config_startup:confval-environment.controlsocket.path) | /var/run/halon/smtpd.ctl | [Control socket](#api_control_sockets) |
### File permissions
Each of the programs are compiled with default paths for Unix domain sockets, temporary directories, and the queue.
The permissions and user/group settings need to be configured however, but the default configuration files comes
with recommended settings as you can see in the */opt/halon/share/examples* directory. This default configuration gives
users of group `staff` the privilege of administration via the control sockets.
Below is a description of why those permissions were chosen:
* *smtpd* runs as user “halon” with umask 0027. This leaves messages on disk with read/write for user “halon”, and
read for group “halon”. The control socket is owned by group “staff” with chmod 0660 which makes it
read/writeable by both “staff”.
### Troubleshooting
If the program doesn’t start,
this is usually due to an erroneous configuration change.
To avoid this, checking the [script](https://docs.halon.io/hsl/) and [configuration](#config)
with *halonconfig*
(and doing a test deploy with [live stage](#cli_config:cli-config))
before configuration deployment is recommended.
Certain types of errors can however not be detected by the syntax checker.
By default, errors are logged to syslog (which with *systemd* is managed
by *journald*):
```console
# journalctl -xe -u halon-smtpd
...
Dec 12 03:12:47 mta1 smtpd[57556]: - listen on 192.0.2.5:25
Dec 12 03:12:47 mta1 smtpd[57556]: Could not load configuration: bind: Cannot assign requested address
...
```
If you want to see startup errors directly on the console you can start *smtpd*
with log sent to foreground (stderr) using the *-f* flag:
```console
# /opt/halon/sbin/smtpd -f
smtpd: Starting (6.12.0)
smtpd: serverid: inbound
smtpd: - listen on 192.0.2.5:25
smtpd: Could not load configuration: bind: Cannot assign requested address
```
# Configuration
The configuration consists of files in YAML format on disk.
The package comes with multiple example configurations
in */opt/halon/share/examples*.
The YAML configuration files can be
edited directly, and validated against the [JSON schemas](https://docs.halon.io/json-schemas/6.12-stable/) using the `halonconfig` script.
The schemas can be browsed using a tool like Bovet’s [docson](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/6.12-stable/smtpd-app.schema.json).
They are also included in our [Visual Studio Code integration](#vscode), which
provides validation, autocomplete and hover support when editing them.
The YAML configuration allows for byte format and time format syntax in a lot of places.
For example time such as [`servers[].timeout.idle`](#config_smtpd:confval-servers-.timeout.idle) may be specified in the format of `?d?h?m?s`.
Size fields such as [`servers[].phases.data.maxsize`](#config_smtpd:confval-servers-.phases.data.maxsize) may be specified in the format of `?(TiB|GiB|MiB|KiB|B)`.
The YAML configurations support `${ENVIRONMENT:DEFAULT}` replacements.
In order to interpet a `${STRING}` as a replacement use the custom `!Env` tag in the YAML file as well as start the
`smtpd` process with the `--config-env-substitution` flag.
This can be used to template in strings and numbers (such a default values and API keys etc).
```yaml
version: !Env "${VERSION:6.10}"
...
monitor:
apikeys:
- !Env "${MYAPIKEY}"
```
If you are using `halonconfig` and want to cast a environment variable to a `int` or a `float` so it passes the linting you can use the following syntax:
```yaml
version: !Env "${VERSION:6.10}"
...
monitor:
listeners:
port: !Env "tag:yaml.org,2002:int ${MONITORPORT}"
```
> **Warning**
>
> Note that the `tag:yaml.org,2002:*` syntax is not supported by the `smtpd` process itself since it will cast the values regardless, this is why `halonconfig` will strip it out after validating the configuration.
> **Tip**
>
> For services started by `systemd`, command-line flags such as `--config-env-substitution`, and environment variables are set by editing the service configuration.
* Edit the service configuration with `sudo systemctl edit halon`
* Make your edits above the *### Edits below this comment will be discarded* line
* The empty `ExecStart=` line is important. This tells systemd to “forget” the command from the original service file and use your new one instead.
```ini
[Service]
Environment="HALON_TYPE=staging"
ExecStart=
ExecStart=/opt/halon/sbin/smtpd -c /etc/halon/smtpd.yaml --config-env-substitution
```
* Restart the service. You can check which flags are set using `ps aux | grep halon`
* You can check the environment variables of the service using `sudo tr '\0' '\n' < /proc/$(pgrep smtpd)/environ`
**Startup and running configuration**
The [running configuration](#config_smtpd) contains the bulk of the configuration data, including all the
Halon [script](https://docs.halon.io/hsl/). It can be softly reloaded and deployed in a
blue-green fashion using per-connection conditions, using the [control sockets](#api_control_sockets),
[API](https://docs.halon.io/api/), [command line interface](#cli) or web administration.
The default
[startup configuration](#config_startup)
loads the running configuration from */etc/halon/smtpd-app.yaml*
as per the [`environment.appconf`](#config_startup:confval-environment.appconf) directive.
* [Startup configuration](#config_startup)
* [Server directives](#config_startup:server-directives)
* [Monitor directives](#config_startup:monitor-directives)
* [Queue directives](#config_startup:queue-directives)
* [Threads directives](#config_startup:threads-directives)
* [Scripts](#config_startup:scripts)
* [Events](#config_startup:events)
* [Other directives](#config_startup:other-directives)
* [Clustering directives](#config_startup:clustering-directives)
* [Environment directives](#config_startup:environment-directives)
* [Performance and log](#config_startup:performance-and-log)
* [Configuration paths](#config_startup:configuration-paths)
* [Other environment](#config_startup:other-environment)
* [Running configuration](#config_smtpd)
* [Script directives](#config_smtpd:script-directives)
* [Server directives](#config_smtpd:server-directives)
* [Transport directives](#config_smtpd:transport-directives)
* [Monitor directives](#config_smtpd:monitor-directives)
* [Queue directives](#config_smtpd:queue-directives)
* [General directives](#config_smtpd:general-directives)
* [List directives](#config_smtpd:list-directives)
* [Resolver directives](#config_smtpd:resolver-directives)
* [Other directives](#config_smtpd:other-directives)
* [Example](#config_smtpd:example)
* [Suspend configuration](#config_suspend)
* [Suspend directives](#config_suspend:suspend-directives)
* [Policy configuration](#config_policy)
* [Policy directives](#config_policy:policy-directives)
* [Rate directives](#config_policy:rate-directives)
* [Delivery configuration](#config_delivery)
* [Delivery directives](#config_delivery:delivery-directives)
* [Performance tuning](#config_tuning)
* [Thread tuning](#config_tuning:thread-tuning)
* [servers[].threads.event](#config_tuning:servers-threads-event)
* [threads.scripts[].count](#config_tuning:threads-scripts-count)
* [threads.scripts[].concurrent](#config_tuning:threads-scripts-concurrent)
* [queues.threads.pickup](#config_tuning:queues-threads-pickup)
* [resolver.threads.event](#config_tuning:resolver-threads-event)
* [resolver.threads.ready](#config_tuning:resolver-threads-ready)
* [Startup configuration](#config_tuning:startup-configuration)
* [environment.syslog.mask](#config_tuning:environment-syslog-mask)
* [spool.fsync](#config_tuning:spool-fsync)
* [spool.loader.threads.wait](#config_tuning:spool-loader-threads-wait)
* [environment.uuid.version](#config_tuning:environment-uuid-version)
* [Running configuration](#config_tuning:running-configuration)
* [servers[].logging](#config_tuning:servers-logging)
* [servers[].phases.connect.remoteptr](#config_tuning:servers-phases-connect-remoteptr)
* [servers[].phases.data.multipart](#config_tuning:servers-phases-data-multipart)
* [servers[].phases.data.fixheaders](#config_tuning:servers-phases-data-fixheaders)
* [Resolver subsystem](#config_tuning:resolver-subsystem)
* [resolver.concurrency](#config_tuning:resolver-concurrency)
* [resolver.ednsbuffersize](#config_tuning:resolver-ednsbuffersize)
* [resolv.conf](#config_tuning:resolv-conf)
* [systemd](#config_tuning:systemd)
* [K8s](#config_tuning:k8s)
* [Queueing subsystem](#config_tuning:queueing-subsystem)
* [queues.pickup.prereaddata](#config_tuning:queues-pickup-prereaddata)
* [queues.pickup.backpressure](#config_tuning:queues-pickup-backpressure)
* [pooling](#config_tuning:pooling)
* [Memory tuning](#config_tuning:memory-tuning)
* [Disk tuning](#config_tuning:disk-tuning)
# Startup configuration
This is the non-reloadable part of the configuration.
By default, *smtpd* loads it from */etc/halon/smtpd.yaml*.
It is described by, and can be validated with, the
[smtpd.schema.json](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/6.12-stable/smtpd.schema.json).
It most importantly contains the server listen sockets (bind addresses and ports),
PROXY protocol and thread settings.
> **Note**
>
> The example configuration in */opt/halon/share/examples/* that is usually copied
> to */etc/halon/* during installation contains reasonable defaults.
> Normally you only need to modify it when adding additional virtual servers,
> or changing performance related settings such as the number of threads or
> open files resource limit.
## Server directives
The virtual [`servers[]`](#config_smtpd:confval-servers) are configured in the [running configuration](#config_smtpd),
but which port(s) and address(es) to listen to needs to be specified in this file.
Most of the properties applies to servers of both `smtp`, `http`, `https` and `plugin` type, therefor the only which applies to either are explicitly mentioned.
Below is an example for adding a virtual server called “relay”, listening to any IP on port 587:
```yaml
servers:
- id: relay
listeners:
- port: 587
```
### servers[].id
The id of the server. It must match the same type as configured in the [running configuration](#config_smtpd).
### servers[].type
The type of the server, a server can either be an `smtp` server, an `http` / `https` [submission endpoint](#http_submission) or `plugin` [inject plugin](#plugins_native:inject-plugin). The default is `smtp`.
The `https` server type requires a *servers[].tls.certs.cert* to be configured in the [running configuration](#config_smtpd).
### servers[].listeners[]
Only one listener is supported for servers of type http. Not supported for type plugin
Each virtual server must have one or more listen directives,
which specify which TCP port and address to listen on.
### servers[].listeners[].port
Which TCP port to listen on. Required.
### servers[].listeners[].address
IPv4 or IPv6 address to listen on.
The default is to listen to all IPv4 and IPv6 addresses.
### servers[].listeners[].backlog
The kernel connection backlog. The default is the system default.
### servers[].listeners[].id
An optional ID that can be used for referencing a listen directive from the
running configuration so that for example implicit TLS
can be enabled on a per-listener basis using [`servers[].tls.implicit`](#config_smtpd:confval-servers-.tls.implicit).
### servers[].proxyprotocol[]
Only applies to smtp servers
A list of IP addresses or networks to allow the PROXY protocol (v1 and v2) from.
Set it to *true* to allow it from all IP addresses.
### servers[].threads.event
Not supported for type plugin
The number of [`servers[]`](#config_smtpd:confval-servers) event loop threads, allowing the event loop to take advantage of multiple CPUs.
If given as a number (and not an object with an ID), a event thread pool will be created named after the [`servers[].id`](#config_startup:confval-servers-.id) (prefixed with an underscore).
The default is 4. If configured as an object you may specify an id of a [`servers[].threads.event.id`](#config_startup:confval-servers-.threads.event.id) or
an object with [`servers[].threads.event.count`](#config_startup:confval-servers-.threads.event.count) and [`servers[].threads.event.priority`](#config_startup:confval-servers-.threads.event.priority).
### servers[].threads.event.id
Only applies to smtp servers
The name of the [`threads.events[].id`](#config_startup:confval-threads.events-.id). This property is required if configured with custom event threads.
### servers[].threads.event.count
The number of [`servers[]`](#config_smtpd:confval-servers) event loop threads, allowing the event loop to take
advantage of multiple CPUs. The default is 4.
### servers[].threads.event.priority
Only applies to smtp servers
The priority (nice value) of the thread, from -20 to 19 (default 0). A smaller value means higher priority.
> **Note**
>
> Don’t forget to update this value as well [`environment.rlimit.nice`](#config_startup:confval-environment.rlimit.nice).
### servers[].threads.script
The number of [`servers[]`](#config_smtpd:confval-servers) [script](https://docs.halon.io/hsl/) threads running the hooks
such as [`servers[].phases.eod.hook`](#config_smtpd:confval-servers-.phases.eod.hook). If given as a number (and not an object), a script thread pool will be created
named after the [`servers[].id`](#config_startup:confval-servers-.id) (prefixed with an underscore) with the [`servers[].scripting.concurrency`](#config_startup:confval-servers-.scripting.concurrency) and [`servers[].scripting.stacksize`](#config_startup:confval-servers-.scripting.stacksize).
The default is 32.
### servers[].threads.script.id
The name of the default [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id). This property is required if configured with custom hook threads, and it will be used for any
un-configured hooks.
### servers[].threads.script.hooks.connect
Only applies to smtp servers
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the connect hook.
### servers[].threads.script.hooks.proxy
Only applies to smtp servers
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the proxy hook.
### servers[].threads.script.hooks.helo
Only applies to smtp servers
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the helo hook.
### servers[].threads.script.hooks.auth
Only applies to smtp servers
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the auth hook.
### servers[].threads.script.hooks.mailfrom
Only applies to smtp servers
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the mailfrom hook.
### servers[].threads.script.hooks.rcptto
Only applies to smtp servers
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the rcptto hook.
### servers[].threads.script.hooks.eod
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the eod hook.
### servers[].threads.script.hooks.disconnect
Only applies to smtp servers
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the disconnect hook.
### servers[].scripting.concurrency
The number of concurrent hooks that may be running concurrently on the [`servers[].threads.script`](#config_startup:confval-servers-.threads.script).
The default is [`servers[].threads.script`](#config_startup:confval-servers-.threads.script) (1:1). However using a plugin or function supporting
the HSL suspend functionality more may be running concurrently using cooperative multitasking (M:N).
### servers[].scripting.stacksize
The stack size of a hook. The default is *2097152* bytes (2 MiB).
This setting support byte format syntax (eg. `2MiB`).
This setting shouldn’t be changed without a strong reason and understanding for doing so.
## Monitor directives
A [monitor](#api_monitor) interface allows you to access OpenMetric statistics as well as health check endpoints.
Below is an example for a monitoring interface listening to any IP on port 8080:
```yaml
monitor:
listener:
port: 8080
```
### monitor.type
The type of the monitor, a monitor can either `http` or `https`. The default is `http`.
The `https` monitor type requires a *monitor.tls.certs.cert* to be configured in the [running configuration](#config_smtpd).
### monitor.listener.port
Which TCP port to listen on. This option is mutually exclusive with the [`monitor.listener.path`](#config_startup:confval-monitor.listener.path) setting.
### monitor.listener.address
IPv4 or IPv6 address to listen on.
The default is to listen to all IPv4 and IPv6 addresses.
### monitor.listener.backlog
The kernel connection backlog. The default is the system default.
### monitor.listener.path
Have the monitor socket on a UNIX socket. This option is mutually exclusive with the [`monitor.listener.port`](#config_startup:confval-monitor.listener.port) setting.
### monitor.listener.owner
This option is only supported with the [`monitor.listener.path`](#config_startup:confval-monitor.listener.path) setting.
### monitor.listener.group
This option is only supported with the [`monitor.listener.path`](#config_startup:confval-monitor.listener.path) setting.
### monitor.listener.chmod
This option is only supported with the [`monitor.listener.path`](#config_startup:confval-monitor.listener.path) setting.
## Queue directives
### queues.threads.event
The [queue](#queue) event loop threads, allowing the event loop to take advantage of multiple CPUs.
If given as a number (and not an object without an ID), a event thread pool will be created named “_queues”.
The default is 4.
If configured as an object you may specify an id of a [`queues.threads.event.id`](#config_startup:confval-queues.threads.event.id) or
an object with [`queues.threads.event.count`](#config_startup:confval-queues.threads.event.count) and [`queues.threads.event.priority`](#config_startup:confval-queues.threads.event.priority).
### queues.threads.event.id
The name of the [`threads.events[].id`](#config_startup:confval-threads.events-.id). This property is required if configured with custom event threads.
### queues.threads.event.count
The number of [queue](#queue) event loop threads, allowing the event loop to take
advantage of multiple CPUs. The default is 4.
### queues.threads.event.priority
The priority (nice value) of the thread, from -20 to 19 (default 0). A smaller value means higher priority.
> **Note**
>
> Don’t forget to update this value as well [`environment.rlimit.nice`](#config_startup:confval-environment.rlimit.nice).
### queues.threads.script
The number of [queue](#queue) [script](https://docs.halon.io/hsl/) threads running the
[`scripting.hooks.predelivery`](#config_smtpd:confval-scripting.hooks.predelivery) and
[`scripting.hooks.postdelivery`](#config_smtpd:confval-scripting.hooks.postdelivery) hooks.
If given as a number (and not an object), a script thread pool will be created
named “_queues” with the [`queues.scripting.concurrency`](#config_startup:confval-queues.scripting.concurrency) and [`queues.scripting.stacksize`](#config_startup:confval-queues.scripting.stacksize).
The default is 32.
### queues.threads.script.id
The name of the default [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id). This property is required if configured with custom hook threads, and it will be used for any
un-configured hooks.
### queues.threads.script.hooks.predelivery
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the predelivery hook.
### queues.threads.script.hooks.postdelivery
The name of the [`threads.scripts[].id`](#config_startup:confval-threads.scripts-.id) for the postdelivery hook.
```yaml
queues:
threads:
script:
id: tpool1 # need a default
hooks:
predelivery: tpool1
postdelivery: tpool2
threads:
scripts:
- id: tpool1
count: 8
- id: tpool2
count: 16
```
### queues.threads.pickup
If given as a number of threads preparing messages to be delivered from the active queue.
The default is 1. If configured as an object see [`queues.threads.pickup.count`](#config_startup:confval-queues.threads.pickup.count).
### queues.threads.pickup.count
The number of threads preparing messages to be delivered from the active queue.
The default is 1.
### queues.threads.pickup.priority
The priority (nice value) of the thread, from -20 to 19 (default 0). A smaller value means higher priority.
> **Note**
>
> Don’t forget to update this value as well [`environment.rlimit.nice`](#config_startup:confval-environment.rlimit.nice).
### queues.threads.fetch.priority
The priority (nice value) of the thread fetching messages from the queue, from -20 to 19 (default 0). A smaller value means higher priority.
### queues.threads.rate.priority
The priority (nice value) of the thread refilling queue policy rates, from -20 to 19 (default 0). A smaller value means higher priority.
### queues.threads.release.priority
The priority (nice value) of the thread flagging delivered messages (somewhat like the opposite of [`queues.threads.pickup`](#config_startup:confval-queues.threads.pickup)), from -20 to 19 (default 0). A smaller value means higher priority.
### queues.scripting.concurrency
The number of concurrent hooks that may be running concurrently on the [`queues.threads.script`](#config_startup:confval-queues.threads.script).
The default is [`queues.threads.script`](#config_startup:confval-queues.threads.script) (1:1). However using a plugin or function supporting
the HSL suspend functionality more may be running concurrently using cooperative multitasking (M:N).
### queues.scripting.stacksize
The stack size of a hook. The default is *2097152* bytes (2 MiB).
This setting support byte format syntax (eg. `2MiB`).
This setting shouldn’t be changed without a strong reason and understanding for doing so.
### spool.path
The email queue spool path. This option is mutually exclusive with [`spool.paths[].path`](#config_startup:confval-spool.paths-.path). The default is */var/spool/halon/queue*.
### spool.paths[].path
The email queue spool paths (multiple paths). The paths are used in a round-robin fashion to spread load evenly.
This option is mutually exclusive with [`spool.path`](#config_startup:confval-spool.path). The default is */var/spool/halon/queue*.
During spool in messages are spooled in the order they are specified in the configuration.
### spool.paths[].write
If the path should used when receiving messages (or just consuming existing messages in that path). The default is *true*.
### spool.fsync
If *fsync* operations should be used for the email queue. The default is *true*.
### spool.minfree.inodes
The minimum required free inodes on the *spool.path* disk. The default is *0*.
### spool.minfree.bytes
The minimum required free bytes on the *spool.path* disk.
This setting support byte format syntax (eg. `5GiB`).
The default is *0*.
### spool.loader.threads
If given as a number of threads that read the [spool files](#queue_load:queue-ondisk) into memory during startup.
Those are terminated once the spool is loaded. The default is 32.
If configured as an object see [`spool.loader.threads.count`](#config_startup:confval-spool.loader.threads.count).
### spool.loader.threads.count
The number of threads that read the [spool files](#queue_load:queue-ondisk) into memory during startup.
The default is 32.
### spool.loader.threads.priority
The priority (nice value) of the thread, from -20 to 19 (default 0). A smaller value means higher priority.
> **Note**
>
> Don’t forget to update this value as well [`environment.rlimit.nice`](#config_startup:confval-environment.rlimit.nice).
### spool.loader.wait
If the startup process should wait for the loader to spool in all messages. This allows quotas etc to be properly
updated before new messages are accepted. For faster startup times, this can be disabled. The default is *true*.
### spool.loader.corrupt
How to handle incomplete or corrupt queue (.hqf) files upon startup. The default action is `unlink`.
But it is also possible to `rename` those (appending *.bad*) or `ignore` to do nothing.
### spool.loader.rate
Specify the rate (`files/seconds`) of which messages will be spooled in from disk.
This (in combination with `spool.loader.threads.wait`) allows you to reduce the load caused by spool in while receiving new messages.
The default is no limitation.
### spool.update.threads
The maximum number of synchronous worker threads for API or CLI [queue update](#cli_queue:halonctl-queue)
of the [spool files](#queue_load:queue-ondisk).
The default is 32.
If configured as an object see [`spool.loader.threads.count`](#config_startup:confval-spool.loader.threads.count).
### spool.update.threads.count
The maximum number of synchronous worker threads for API or CLI [queue update](#cli_queue:halonctl-queue)
of the [spool files](#queue_load:queue-ondisk).
The default is 32.
### spool.update.threads.priority
The priority (nice value) of the thread, from -20 to 19 (default 0). A smaller value means higher priority.
> **Note**
>
> Don’t forget to update this value as well [`environment.rlimit.nice`](#config_startup:confval-environment.rlimit.nice).
### spool.update.background.threads.count
The maximum number of asynchronous (background) worker threads for API or CLI [queue update](#cli_queue:halonctl-queue)
of the [spool files](#queue_load:queue-ondisk).
The default is 32.
### spool.update.background.threads.priority
The priority (nice value) of the thread, from -20 to 19 (default 0). A smaller value means higher priority.
> **Note**
>
> Don’t forget to update this value as well [`environment.rlimit.nice`](#config_startup:confval-environment.rlimit.nice).
### spool.update.background.rate
Specify the rate (`messages/seconds`) of which messages will be updated.
This allows you to reduce the impact when doing bulk update while processing messages.
The default is no limitation.
## Threads directives
### Scripts
Custom script thread pools allows you to control on which script thread pools a script / hook is executed.
#### threads.scripts[].id
The name of the script thread pool
#### threads.scripts[].count
The number of scripts threads running a hooks
such as [`servers[].phases.eod.hook`](#config_smtpd:confval-servers-.phases.eod.hook).
The default is 32.
#### threads.scripts[].priority
The priority (nice value) of the thread, from -20 to 19 (default 0). A smaller value means higher priority.
> **Note**
>
> Don’t forget to update this value as well [`environment.rlimit.nice`](#config_startup:confval-environment.rlimit.nice).
#### threads.scripts[].concurrency
The number of concurrent hooks that may be running concurrently.
The default is [`threads.scripts[].count`](#config_startup:confval-threads.scripts-.count) (1:1). However using a plugin or function supporting
the HSL suspend functionality more may be running concurrently using cooperative multitasking (M:N).
#### threads.scripts[].stacksize
The stack size of a hook. The default is *2097152* bytes (2 MiB).
This setting support byte format syntax (eg. `2MiB`).
This setting shouldn’t be changed without a strong reason and understanding for doing so.
### Events
Custom event thread pools allows you to control on which thread pools a network event is processed.
#### threads.events[].id
The name of the events thread pool
#### threads.events[].count
The number of events threads running network events.
The default is 4.
#### threads.events[].priority
The priority (nice value) of the thread, from -20 to 19 (default 0). A smaller value means higher priority.
> **Note**
>
> Don’t forget to update this value as well [`environment.rlimit.nice`](#config_startup:confval-environment.rlimit.nice).
## Other directives
### resolver.threads.event
Number of DNS resolver event pool threads. The default is 1.
### resolver.threads.ready
Number of threads processing ready resolvings. The default is 1.
### resolver.ednsbuffersize
The EDNS buffer size. The default is `1280`, as per [RFC8200](https://www.rfc-editor.org/rfc/rfc8200.html).
You can change this value if necessary to suit your network, e.g. to reduce UDP packet fragmentation and fallback to TCP.
### plugins[]
Load the following plugins into the MTA. The `id` and `path` properties are required. An optional *config* object or YAML path may be specified for the plugin’s configuration. A similar *config* property is also available in the [running configuration](#config_smtpd).
```yaml
plugins:
- id: test
path: "/opt/halon/plugins/test.so"
config:
myval: true
```
### plugins[].config
The startup configuration. This property may be either a string or an YAML object.
If this property is given as a string it must point to a YAML file on disk.
### pki.private[]
Array of private keys, possibly with X.509 certificates, for use with [`servers[]`](#config_smtpd:confval-servers) and [script](https://docs.halon.io/hsl/) functions such as
PKCS7, RSA, DKIM, client certificates, etc.
The `id` and `privatekey` properties are required, and `certificate` is optional.
The private key and certificate should have either a `path` or `data` property.
```yaml
pki:
private:
- id: selfsigned
certificate:
data: |-
-----BEGIN CERTIFICATE-----
...
privatekey:
data: |-
-----BEGIN PRIVATE KEY-----
...
```
> **Note**
>
> It is also possible to add those to the [running configuration](#config_smtpd),
> but for privilege separation reasons it’s normally recommended to define private keys here
> instead, as this startup configuration is read before the privilege drop.
> It is however possible to load the private key from a path in this startup configuration,
> and load the certificate from a path in running configuration, which allows you to softly
> reload the certificate when it changes, as long as the private key stays the same.
### scripting.ffi
Enable the use of [FFI](https://docs.halon.io/hsl/functions.html#ffi) functions
from the [script](https://docs.halon.io/hsl/) language. The default is false.
### scripting.rootpath
Enable accessing files from disk via the [File](https://docs.halon.io/hsl/functions.html#File) class
and the [`scripting.files[]`](#config_smtpd:confval-scripting.files) path, relative from the specified root path. The default is to
not allow accessing files on disk.
### scripting.cache.scope
Configure the [script](https://docs.halon.io/hsl/) language `cache []` statement scope. It can either be `global` (a persistent cache between config reloads)
or `config` (to be per config reload or live stage, shared between all hooks).
The default scope is per `config`.
## Clustering directives
It’s possible to configure sharing of *concurrency*, *connectinterval* and *rate* settings across a cluster of nodes, using the [Delivery Orchestrator](/clusterd/) service.
See also [Cluster-aware policy](#queue_cluster:cluster-aware-policy).
### cluster.address
The peer address of the `clusterd` service.
### cluster.port
The peer port of the `clusterd` service. The default is `2530`.
### cluster.startup.hosts
If it cannot connect on startup, it will default to this many hosts until (when dividing concurrency and rates).
The default is to wait for a connection before starting.
This setting can be set even when [`cluster.address`](#config_startup:confval-cluster.address) is not set, to set a static number of hosts to divide concurrency and rates by, without using [Delivery Orchestrator](/clusterd/).
### cluster.policy.sharedconcurrency
If the concurrency should be shared in the cluster (this is the opposite of evenly divide based on the number of hosts connected).
The default is `true`.
### cluster.tls.id
The id of the client certificate TLS (PKI) certificate to use.
### cluster.tls.verify
If the server peer certificate should be verified. The default is `true`.
### cluster.tls.verifyname
Hostname to verify against the server certificate’s CN and SAN.
## Environment directives
The default startup configuration that came with the installation package
contains reasonable defaults for your platform. Some settings should however
be revised.
### Performance and log
Those settings are typically configured depending on your
system and use case.
#### environment.rlimit.nofile
Set the max number of open file descriptors (this changes the current process limit from system default `ulimit -n`).
This value should be calculated from other limits to allow the server to allocate system resources as needed.
There is no downside of specifying a value larger than needed, so there is no reason to be conservative (just make sure it fits within system limits).
The most dominant values in the configuration has the biggest impact on the result, so for simplicity in most cases you may use a quite simple formula like this.
The sum of [`servers[].concurrency.total`](#config_smtpd:confval-servers-.concurrency.total), [`queues.concurrency.total`](#config_smtpd:confval-queues.concurrency.total) and [`queues.pooling.size`](#config_smtpd:confval-queues.pooling.size) multiplying
by `5` and adding a fixed value of eg. `10000` (to safely cover other limits such a script thread usage and DNS etc). Even if this value comes out twice or more than what is actual
needed it should be used as a safety margin.
```default
((1000 + 10000 + 1000) * 5) + 10000 = 70000
```
Given the potential complexity of the calculation, which also depends on how the scripting is used, we cannot safely list all other values that need to be considered.
In most cases we estimate that these should all fit within the margin of the calculation (both in multiplication factor and also the fixed value added).
However, for transparency here is a few.
* smtpd’s file descriptors, used for various things
* [`servers[].listeners[]`](#config_startup:confval-servers-.listeners)
* [`servers[].threads.script`](#config_startup:confval-servers-.threads.script)
* [`queues.threads.script`](#config_startup:confval-queues.threads.script)
* [`spool.update.threads`](#config_startup:confval-spool.update.threads)
* [`resolver.threads.event`](#config_startup:confval-resolver.threads.event)
The Linux kernel has two limits, the `sysctl fs.file-max` which is the upper limit of fd in the OS kernel and `sysctl fs.nr_open` which controls the upper limit of ulimit command. It’s important that they are larger than the [`environment.rlimit.nofile`](#config_startup:confval-environment.rlimit.nofile) specified.
#### environment.rlimit.nice
Set the the highest priority (lowest nice value) a thread can request (this changes the current process limit from system default `ulimit -e`), from -20 to 19 (default 0). A smaller value means higher priority.
#### environment.syslog.mask
If you are using `systemd-journald` for `syslog()`, we strongly recommend
masking away `LOG_INFO` (non-error email transaction) for performance reasons
by setting this option to 191.
For transaction logging you can use a module from our [script library](https://docs.halon.io/go/scriptlibrary)
such as Elastic, libjlog or syslog directly to rsyslog.
The default is no mask.
#### environment.syslog.ident
The syslog identity. The default is the program name.
#### environment.syslog.facility
The syslog facility. Valid options are `daemon`, `mail`, `user`, `local0`, `local1`, `local2`, `local3`, `local4`, `local5`, `local6` and `local7`. The default is `mail`.
#### environment.syslog.pid
Log the process ID. The default is false.
### Configuration paths
Those paths can normally be left unchanged.
#### environment.appconf
From where to load the [running configuration](#config_smtpd).
The default is */etc/halon/smtpd-app.yaml*.
#### environment.policyconf
From where to load the [active queue policies](#queue_policy:queue-policy).
The default is */etc/halon/smtpd-policy.yaml*.
#### environment.suspendconf
From where to load the [active queue suspends](#queue_suspend:queue-suspend).
The default is */etc/halon/smtpd-suspend.yaml*.
#### environment.deliveryconf
From where to load the [active queue delivery settings](#queue_delivery:queue-delivery).
The default is */etc/halon/smtpd-delivery.yaml*.
#### environment.licensekey
From where to load the license.
The default is `/etc/halon/license.key`.
A license key may also be loaded from the environment variable `HALON_LICENSEKEY`,
the should contain the license file content base64 encoded.
The environment variable has precedence over this [`environment.licensekey`](#config_startup:confval-environment.licensekey) setting.
#### environment.pluginspath
From where to load plugins.
The default is `/etc/halon/plugins`.
### Other environment
Those settings should normally be left unchanged.
The default startup configuration in */opt/halon/examples*
that came with the installation package should contain
correct parameters for your operating system or distribution.
#### environment.controlsocket.path
Have the control socket on a UNIX socket. This option is mutually exclusive with the [`environment.controlsocket.port`](#config_startup:confval-environment.controlsocket.port) setting.
#### environment.controlsocket.owner
This option is only supported with the [`environment.controlsocket.path`](#config_startup:confval-environment.controlsocket.path) setting.
#### environment.controlsocket.group
This option is only supported with the [`environment.controlsocket.path`](#config_startup:confval-environment.controlsocket.path) setting.
#### environment.controlsocket.chmod
This option is only supported with the [`environment.controlsocket.path`](#config_startup:confval-environment.controlsocket.path) setting.
#### environment.controlsocket.port
Have the control socket on a TCP port. This option is mutually exclusive with the [`environment.controlsocket.path`](#config_startup:confval-environment.controlsocket.path) setting.
#### environment.controlsocket.address
Have the control socket listen on a specific IP address. This option is only supported with the [`environment.controlsocket.port`](#config_startup:confval-environment.controlsocket.port) setting.
#### environment.controlsocket.tls
TLS settings for the control socket when using [`environment.controlsocket.port`](#config_startup:confval-environment.controlsocket.port). The default is no TLS.
#### environment.controlsocket.tls.cert
Which [`pki.private[]`](#config_startup:confval-pki.private) to use for TLS on the control socket.
#### environment.controlsocket.tls.cacerts[]
A list of X.509 CA certificates for client certificate authentication, either a `path` or `data` property.
This feature will disable looking at the system-wide certificate store.
#### environment.controlsocket.tls.lookupdirs.paths[]
A list of paths to lookup `openssl rehash`ed certificates and CRLs.
#### environment.controlsocket.tls.lookupdirs.crl
If CRL checking should be done (searching in the lookupdirs). The default is `false`.
#### environment.controlsocket.tls.crls[]
A list of X.509 Certificate revokelists (CRL) for client certificate authentication, either a `path` or `data` property.
#### environment.controlsocket.tls.clientcerts[]
A list of X.509 certificates for client certificate authentication, either a `path` or `data` property.
This feature is mutually exclusive with clientnames.
#### environment.controlsocket.tls.clientnames[]
The client certificates names that the server should trust. Supports wildcards eg. `.example.net`. This feature is mutually exclusive with clientcerts.
#### environment.controlsocket.tls.client
The TLS settings for the [command line interface](#cli).
#### environment.controlsocket.tls.client.certificate
The client certificate to use. This setting should have either a `path` or `data` property.
#### environment.controlsocket.tls.client.privatekey
The private key for the client certificate. This setting should have either a `path` or `data` property.
#### environment.controlsocket.tls.client.verify
If the server peer certificate should be verified. The default is `true`.
#### environment.controlsocket.tls.client.verifyname
Hostname to verify against the server certificate’s CN and SAN.
#### environment.controlsocket.backlog
The kernel connection backlog. The default is the system default.
#### environment.controlsocket.logging.audit
If detailed audit logging should be enabled. The default is `true`.
#### environment.privdrop.user
The user to run the process as.
#### environment.privdrop.group
The group to run the process as.
#### environment.publicsuffix
The path to a publicsuffix list (publicsuffix.org).
#### environment.umask
Change the umask for file creation. The default is often *022*.
#### environment.uuid.version
There are two supported methods of UUID generation: time based version 1 and random version 4. The default is *1* and it requires safe UUIDs to be generated (which is tested for upon smtpd startup). Safe in this context means it has to be thread-safe; assisted by the operating system/implementation (either by a syscall, a daemon such as *uuidd* or file locking). Depending on the environment the preferred way may differ. If you cannot use any of these, we also offer version *4* which is random based. The probably of collision of random UUIDs are so low they should be considered [safe](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)).
# Running configuration
The SMTP server (*smtpd*) handles all email going in and out,
including the [queue subsystem](#queue).
As described in the [SMTP server script manual](https://docs.halon.io/hsl/archive/6.12-stable/smtpserver.html)
it is primarily configured using hooks on the SMTP command phases;
HELO, MAIL FROM, RCPT TO, etc.
Similarly, the queue can be configured using pre- and post-delivery hooks
as described in the [queue script manual](https://docs.halon.io/hsl/archive/6.12-stable/smtpqueue.html).
Virtual [queues](#queue) are defined by the active queue policy (using concurrency and rate).
This is the reloadable part of the configuration,
which contains the bulk of the configuration data.
By default, this is loaded from file */etc/halon/smtpd-app.yaml* and can be reloaded with the command:
```console
$ halonctl config reload
```
The file contents are described by, and can be validated with, the
[smtpd-app.schema.json](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/6.12-stable/smtpd-app.schema.json).
It can be softly reloaded and deployed in a
blue-green fashion using per-connection conditions
from [Visual Studio Code](#vscode),
[command line interface](#cli_config) or [control sockets](#api_control_sockets).
To use a non-default filename for the running configuration, set the [start configuration](#config_startup)’s
[`environment.appconf`](#config_startup:confval-environment.appconf) directive.
## Script directives
The [script](https://docs.halon.io/hsl/) is normally edited as individual files
using [Visual Studio Code](#vscode) or simply a text editor.
Those are then checked and “packed” into an actual configuration
file by the *halonconfig* script.
For example, two script files called *src/hooks/rcptto/smtplookup.hsl*
and *src/hooks/queue/predelivery.hsl* will result in:
```yaml
scripting:
hooks:
rcptto:
- id: smtplookup
data: |
...
predelivery: |
...
```
and the “smtplookup” RCPT TO script can then be hooked into a server with:
```yaml
servers:
- id: inbound
phases:
rcptto:
hook: smtplookup
```
The default configuration in */opt/halon/share/examples* contains the folder structure
for all the different hook types.
Since the SMTP server can have multiple virtual [`servers[]`](#config_smtpd:confval-servers)
(one inbound on port 25 and one outbound on port 587 for example),
the script hooks are designed so that there can be multiple scripts
of a given hook type which are then mapped to servers. There is however
just one set of pre- and post-delivery scripts controlling the queue.
### scripting.hooks.connect[]
An array of [connect](https://docs.halon.io/hsl/archive/6.12-stable/connect.html) scripts.
Attached to a virtual server via [`servers[].phases.connect.hook`](#config_smtpd:confval-servers-.phases.connect.hook).
### scripting.hooks.helo[]
An array of [HELO/EHLO](https://docs.halon.io/hsl/archive/6.12-stable/helo.html) scripts.
Attached to a virtual server via [`servers[].phases.helo.hook`](#config_smtpd:confval-servers-.phases.helo.hook).
### scripting.hooks.auth[]
An array of [AUTH](https://docs.halon.io/hsl/archive/6.12-stable/auth.html) scripts.
Attached to a virtual server via [`servers[].phases.auth.hook`](#config_smtpd:confval-servers-.phases.auth.hook).
### scripting.hooks.mailfrom[]
An array of [MAIL FROM](https://docs.halon.io/hsl/archive/6.12-stable/mailfrom.html) scripts.
Attached to a virtual server via [`servers[].phases.mailfrom.hook`](#config_smtpd:confval-servers-.phases.mailfrom.hook).
### scripting.hooks.rcptto[]
An array of [RCPT TO](https://docs.halon.io/hsl/archive/6.12-stable/rcptto.html) scripts.
Attached to a virtual server via [`servers[].phases.rcptto.hook`](#config_smtpd:confval-servers-.phases.rcptto.hook).
### scripting.hooks.eod[]
An array of [end-of-DATA](https://docs.halon.io/hsl/archive/6.12-stable/eodonce.html) scripts.
Attached to a virtual server via [`servers[].phases.eod.hook`](#config_smtpd:confval-servers-.phases.eod.hook).
### scripting.hooks.proxy[]
An array of [proxy](https://docs.halon.io/hsl/archive/6.12-stable/proxy.html) scripts.
Attached to a virtual server via [`servers[].phases.proxy.hook`](#config_smtpd:confval-servers-.phases.proxy.hook).
### scripting.hooks.disconnect[]
An array of [disconnect](https://docs.halon.io/hsl/archive/6.12-stable/disconnect.html) scripts.
Attached to a virtual server via [`servers[].phases.disconnect.hook`](#config_smtpd:confval-servers-.phases.disconnect.hook).
### scripting.hooks.predelivery
The [pre-delivery script](https://docs.halon.io/hsl/archive/6.12-stable/predelivery.html) executed as the first step
of loading an email into the active [queue](#queue).
### scripting.hooks.postdelivery
The [post-delivery script](https://docs.halon.io/hsl/archive/6.12-stable/postdelivery.html) executed as the last step
after a [delivery attempt](#queue).
### scripting.files[]
An array of virtual files used by the [script](https://docs.halon.io/hsl/) hooks.
It is most commonly used for imported script modules, include files,
CSV lists and plain text files. As with the script hooks, this is normally
populated by the *halonconfig* script. It is however also possible to load files
from disk (on reload) if allowed by [`scripting.rootpath`](#config_startup:confval-scripting.rootpath).
```yaml
scripting:
files:
- id: blocklist.csv
path: config/blocklist.csv
```
## Server directives
The virtual servers first need to be defined in the [startup configuration](#config_startup),
as they specify which port(s) and address(es) to listen to (which cannot be softly reloaded).
All other properties are then defined in this reloadable configuration. Many of the properties only applies to
servers of type `smtp`, therefore the properties which applies to `http` and `plugin` servers are explicitly mentioned.
### servers[]
An array of virtual servers, which for example define which [script](https://docs.halon.io/hsl/) hooks should be used.
They are connected to the directives of the [startup configuration](#config_startup) via their IDs,
as per the example below:
*smtpd-app.yaml*
```yaml
servers:
- id: default
transport: mailserver
phases:
rcptto:
hook: smtplookup
eod:
hook: inbound
concurrency:
total: 10000
ip: 10
- id: relay
transport: mx
phases:
rcptto:
hook: relaytrusted
eod:
hook: outbound
```
*smtpd.yaml*
```yaml
servers:
- id: default
listeners:
- port: 25
backlog: 2048
- id: relay
listeners:
- port: 587
address: 192.168.0.100
```
### servers[].id
The id of the server. It must match the same type as configured in the [startup configuration](#config_startup).
### servers[].type
The type of the server. It must match the same type as configured in the [startup configuration](#config_startup).
The default is `smtp`.
### servers[].concurrency.ip
The maximum total number of SMTP connections that the server accepts from a single IP address.
The default is unlimited.
### servers[].concurrency.total
Also applies to http servers
The maximum total number of SMTP connections that the server accepts.
Halon uses an asynchronous model, and can therefore support a much larger
number compared to servers that use a process- or thread-based model.
You must revise the value of [`environment.rlimit.nofile`](#config_startup:confval-environment.rlimit.nofile) so that
the server has enough file descriptor resources available.
The default is 1000.
*smtpd-app.yaml*
```yaml
servers:
- id: default
concurrency:
total: 20000
```
*smtpd.yaml*
```yaml
environment:
rlimit:
nofile: 50000
```
### servers[].concurrency.reserved.total
The number of reserved connection slots. This functionality is reserving slots within the total number of allowed
connections (see [`servers[].concurrency.total`](#config_smtpd:confval-servers-.concurrency.total)).
### servers[].concurrency.reserved.remoteips
An array of IP addresses which are allowed to use the reserved slots.
### servers[].concurrency.reserved.priority
The initial priority of reserved slots.
### servers[].priority.remoteips
An array of IP addresses which are should have higher initial priority on connect (prio=1).
### servers[].phases.connect.hook
Which [`scripting.hooks.connect[]`](#config_smtpd:confval-scripting.hooks.connect) to run on connect. The default is no script; to accept the connection.
### servers[].phases.connect.remoteptr
Should a forward-confirmed reverse DNS lookup be performed on the connecting IP,
for use in script and trace information (*Received* header).
The default is *false*.
### servers[].phases.helo.hook
Which [`scripting.hooks.helo[]`](#config_smtpd:confval-scripting.hooks.helo) to run on HELO/EHLO. The default is no script, to send a standard response.
### servers[].phases.helo.required
Should the client be required to send HELO/EHLO before proceeding. The default is *false*.
### servers[].phases.auth.hook
Which [`scripting.hooks.auth[]`](#config_smtpd:confval-scripting.hooks.auth) to run on AUTH. The default is no script, which disables AUTH.
### servers[].phases.auth.mechanisms
Which SASL mechanisms to announce. The default is the built-in types *LOGIN* and *PLAIN*.
If additional mechanisms are announced, they need to be implemented
by the [`scripting.hooks.auth[]`](#config_smtpd:confval-scripting.hooks.auth) script.
### servers[].phases.auth.tlsrequired
Should TLS be required for *AUTH*. The default is *false*.
### servers[].phases.auth.apikeys
Only applies to http servers
List of allowed API keys for the HTTP server (sent in the *X-API-Key* header).
### servers[].phases.mailfrom.hook
Which [`scripting.hooks.mailfrom[]`](#config_smtpd:confval-scripting.hooks.mailfrom) to run on *MAIL FROM*. The default is no script; to accept the sender.
### servers[].phases.mailfrom.unqualified
If unqualified addresses (local part only) should be accepted. The default is *false*.
### servers[].phases.mailfrom.maxmessages
Maximum number of messages per connection (after which a 4xx error is returned). The default is to have no limit.
### servers[].phases.rcptto.hook
Which [`scripting.hooks.rcptto[]`](#config_smtpd:confval-scripting.hooks.rcptto) to run on *RCPT TO*. The default is no script; to accept the recipient.
### servers[].phases.rcptto.unqualified
If unqualified addresses (local part only) should be accepted. The default is *false*.
### servers[].phases.rcptto.maxrecipients
Maximum number of recipients per message (after which a 4xx error is returned). The default is 1000.
### servers[].phases.rcptto.maxdomains
Maximum number of unique recipient domains per message.
### servers[].phases.data.maxsize
Also applies to http servers
How large *DATA* response to accept. The setting support byte format syntax (eg. `10MiB`). The default is *10485760* bytes (10 MiB).
This setting can also be changed in the [connect](https://docs.halon.io/hsl/archive/6.12-stable/connect.html) scripts for SMTP servers.
### servers[].phases.data.maxhops
Number of Received headers allowed (loop detection). The default is 50.
### servers[].phases.data.maxheadersize
The max message header size (in bytes). The setting support byte format syntax (eg. `512KiB`). The default is *524288* bytes (512 KiB).
### servers[].phases.data.receivedheader
Also applies to http servers
Add a Received header. This property may also be an object controlling the format of the added Received header. The default is *true*.
### servers[].phases.data.receivedheader.from
Include the `from peer (...)` information. The default is `true`.
### servers[].phases.data.receivedheader.for
For messages with only one recipient add `for ` information. The default is `false`.
### servers[].phases.data.receivedheader.tls
For messages received with TLS add `tls` information (such as protocol version and ciphers). The default is `false`.
### servers[].phases.data.fixheaders
Repair RFC822 messages with broken headers, by injecting a *\\r\\n* before the broken header, making it part of the message body. This is the default behavior of most MTAs. The default is `false`.
### servers[].phases.data.multipart
Also applies to http and plugin servers
If set to `false`, disable MIME multipart parsing. The message will still contain the RFC822 headers, but no MIME parts will be parsed.
### servers[].phases.data.multipart.maxparts
The maximum message MIME parts to allow (before rejecting the message as invalid). The default is `512`.
### servers[].phases.data.multipart.fixheaders
Repair MIME multipart messages with broken headers, by injecting a *\\r\\n* before the broken header, making it part of the message part body. The default is `false`.
### servers[].phases.data.multipart.strict
Enforce stricter multipart boundary parsing. The default is `true`.
### servers[].phases.eod.hook
Also applies to http and plugin servers
Which [`scripting.hooks.eod[]`](#config_smtpd:confval-scripting.hooks.eod) to run on end-of-*DATA*. The default is no script; to queue the email for all recipients.
### servers[].phases.proxy.patterns[]
An array of SMTP command “patterns” before which to run the [`servers[].phases.proxy.hook`](#config_smtpd:confval-servers-.phases.proxy.hook) script.
The SMTP commands are matched up to the length of the pattern, case-insensitive.
For example, specifying just one letter (such as “q”) will run the proxy script before all
SMTP commands starting with that letter (such as *QUIT*).
The default is to run the proxy script before all commands.
### servers[].phases.proxy.hook
Which [`scripting.hooks.proxy[]`](#config_smtpd:confval-scripting.hooks.proxy) to run on before commands matched by
[`servers[].phases.proxy.patterns[]`](#config_smtpd:confval-servers-.phases.proxy.patterns).
The default is no script.
### servers[].phases.disconnect.hook
Which [`scripting.hooks.disconnect[]`](#config_smtpd:confval-scripting.hooks.disconnect) to run on disconnect. The default is no script.
### servers[].hostname
Also applies to http and plugin servers
The hostname shown in the banner and HELO/EHLO. The default is to use the system hostname.
This can be overridden by the [`scripting.hooks.connect[]`](#config_smtpd:confval-scripting.hooks.connect) and
[`scripting.hooks.helo[]`](#config_smtpd:confval-scripting.hooks.helo) scripts.
### servers[].proxy.xforwardedfor
Only applies to http servers
Allow a HTTP proxy to change the notion of `remoteip` by sending the *X-Forwarded-For* header.
### servers[].extensions.smtputf8
Enable support for the SMTPUTF8 extension. The default is *false*.
### servers[].extensions.chunking
Enable support for the CHUNKING extension. The default is *true*.
### servers[].extensions.pipelining
Enable support for the PIPELINING extension. The default is *true*.
To enforce strict protocol synchronization rules use the [`servers[].compatibility.synchronization`](#config_smtpd:confval-servers-.compatibility.synchronization) setting.
### servers[].extensions.xclient
An array of IP addresses or networks to allow XCLIENT from, or *true* to allow from anyone. The default is *false*; not from anyone.
The following session properties may be updated *ADDR*, *PORT*, *NAME*, *HELO* and *LOGIN*.
### servers[].logging.protocol
Enable SMTP protocol logging (more verbose). The default is *false*.
### servers[].logging.hook
Also applies to http and plugin servers
Enable script hooks logging (pre and post-amble). The default is *true*.
### servers[].timeout.idle
Number of seconds of inactivity before disconnecting a client.
This setting support time format syntax (eg. `5m`).
The default is *300* seconds for smtp and *60* seconds for http interfaces.
> **Note**
>
> Halon uses an asynchronous model, and can support a much larger
> number of connected clients compared to servers that use a process- or thread-based model.
> Therefore, there is usually no reason to lower this setting.
### servers[].commands.maxidle
The max amount of idle commands (RSET, NOOP) before disconnect.
The default is 100.
### servers[].commands.maxbad
The max amount of bad commands (syntax errors or bad commands) before disconnect.
The default is 10.
### servers[].tls.certs.cert
Also applies to https servers
Which [`pki.private[]`](#config_startup:confval-pki.private) to use for TLS. The default is no TLS.
Can also be an array of [`pki.private[]`](#config_startup:confval-pki.private) in case you want to support both RSA and ECDSA at the same time (Only supported by smtp servers).
Custom TLS certificates can be dynamically choosen in the connect hook (Only supported by smtp servers).
### servers[].tls.certs.sni
An array of certs to use with SNI. The *cert* can either be a [`pki.private[]`](#config_startup:confval-pki.private) or
an array of [`pki.private[]`](#config_startup:confval-pki.private) in case you want to support both RSA and ECDSA at the same time.
The CN/SANs of the certificate will be used for matching.
An optional list of subject names to use instead of the one(s) in the certificate
can be provided.
The default is no SNI.
```yaml
servers:
- id: default
tls:
certs:
cert: defaultpki
sni:
- cert: otherpki
- cert: yetanotherpki
subjects:
- "test2.example.org"
```
### servers[].tls.protocols
Which TLS protocols to support. Available protocols are *SSLv2*, *SSLv3*, *TLSv1*, *TLSv1.1*, *TLSv1.2*, and *TLSv1.3*. Either specify a list of explicitly supported protocols or a list of *!* unsupported protocol (meaning that all not excluded are supported, which may support future version of TLS). The default is *!SSLv2,!SSLv3* (meaning that TLSv1 and later are supported).
### servers[].tls.ciphers
Which TLS ciphers to support (<=TLSv1.2). The ciphers supported in each group may differ between different version of OpenSSL and SSL/TLS versions (see the *openssl ciphers* command). The default is *aNULL:-aNULL:HIGH:MEDIUM:+RC4:@STRENGTH*.
### servers[].tls.ciphersuites
Which TLS cipher suites to support (TLSv1.3). The cipher suites supported in each group may differ between different version of OpenSSL and SSL/TLS versions (see the *openssl ciphers* command).
### servers[].tls.groups
Which TLS key exchange groups to support. The TLS key exchange groups supported in each group may differ between different version of OpenSSL and SSL/TLS versions.
### servers[].tls.clientcert
An array of IP addresses or networks to request client certificates from, or *true* to request from anyone. The default is *false*; not from anyone.
### servers[].tls.implicit
An array of startup configuration [`servers[].listeners[]`](#config_startup:confval-servers-.listeners) IDs to enable implicit TLS for,
or *true* enable implicit TLS for all listeners on this virtual server. The default is *false*; no implicit TLS.
To enable implicit TLS on port 465, but not 587, you need reference the listener on port 465 by its ID *idof465*:
*smtpd.yaml*
```yaml
- id: relay
listeners:
- port: 465
id: idof465
- port: 587
```
*smtpd-app.yaml*
```yaml
servers:
- id: relay
tls:
implicit:
- idof465
```
### servers[].productname
Also applies to http and plugin servers
The software name. The default is *Halon*. This is used in eg. Received headers.
### servers[].transport
Also applies to http and plugin servers
The default [`transportgroups[].transports[]`](#config_smtpd:confval-transportgroups-.transports) to queue the email on.
This can be overridden by the [`scripting.hooks.eod[]`](#config_smtpd:confval-scripting.hooks.eod) script.
### servers[].compatibility.barelf
Only applies to smtp servers
The SMTP RFC requires \\ as end-of-line and end-of-data delimiters. However the use of bare \ is
sometimes used in legacy clients, printers or various tools.
* Halon always allows bare \ as end-of-line delimiter for SMTP commands.
* Halon always converts any bare \ in the message DATA into \\.
However, there is a known issue when there is a mismatch of the end-of-data delimiter (\\.\\)
between the client and server, causing potential message injection attacks if both do not agree on when
to terminate the message (*smuggling*).
Halon auto-detect by default (if not configured) if it should allow the combination of bare \.\ as end-of-data sequence
based on the first header line terminator.
This assumes that non-conforming client will use \ as line delimiter for the entire data stream.
Known *smuggling* attacks are not practically possible with the default auto-detect setting.
This setting may be set to
> * `true` to always allow bare \ (no auto-detect)
> * `false` to never allow bare \
> * *array[] of IP-addresses/networks* to only allow bare \ from those hosts
### servers[].compatibility.synchronization
Only applies to smtp servers
Enable this feature to enforce stricter protocol synchronization rules.
This feature works in combination with [`servers[].extensions.pipelining`](#config_smtpd:confval-servers-.extensions.pipelining).
* without PIPELINING (rfc5321) all commands are synchronization points
* with PIPELINING (rfc2920) only check synchronization points at certain commands
Due to the nature of TCP and network delays,
it’s not always possible to detect protocol violations.
The default is *false*.
## Transport directives
In Halon, every email is queued with a text label called “transport”, which normally
exists in the configuration as a definition for how the email should be delivered.
The parameters defined by the configured transport can be overridden in the pre- and
post-delivery [script](https://docs.halon.io/hsl/), as in the [`addresses[]`](#config_smtpd:confval-addresses) example.
### transportgroups[]
The transport groups are simply a logical grouping of transports in the configuration,
and transports inherit parameters from the group.
In addition to all the transport directives listed below,
transport groups have an `id`, and an array of `transports`.
### transportgroups[].transports[]
Each of the transports have an `id` in addition to all the transport directives.
The example below has two “outbound” MX transports;
one with and other without DANE encryption.
Then there are two other transports called “smarthost1” and “inbound”
in a group called “default”.
```yaml
transportgroups:
- id: mxes
retry:
count: 30
intervals:
- interval: 60
- interval: 900
- interval: 3600
notify: true
- interval: 7200
- interval: 10800
dsn:
transport: mx
connection:
sourceip:
ipv4:
- "out1"
- "out2"
ipv6:
- "out1v6"
- "out2v6"
transports:
- id: mx
session:
tls:
mode: optional
- id: mxdane
session:
tls:
mode: dane
- id: default
transports:
- id: smarthost1
connection:
server: "smtp-out.example.org"
port: 587
sourceip:
ipv6: false
session:
authentication:
username: foo
password: bar
- id: inbound
connection:
server: "dovecot.example.com"
port: 24
session:
protocol: lmtp
```
The transport directives listed below are valid for both transport groups and transports.
### transportgroups[].connection.server
By default, an MX lookup is performed to determine the next hop destination. By setting the
server directive, you can specify an IPv4, IPv6 or hostname as destination or alternatively
an object containing [`transportgroups[].connection.server.host`](#config_smtpd:confval-transportgroups-.connection.server.host) and [`transportgroups[].connection.server.mx`](#config_smtpd:confval-transportgroups-.connection.server.mx). This is normally
the case for “inbound” traffic to a mailbox server (maybe over LMTP) or “outbound” delivery
via a so-called smarthost.
### transportgroups[].connection.server.host
This configuration option is to determine the next hop destination. The default is the recipients domain.
### transportgroups[].connection.server.mx
This configuration option is used to control if an MX or A/AAAA query is used for the [`transportgroups[].connection.server.host`](#config_smtpd:confval-transportgroups-.connection.server.host) domain. The default is false.
### transportgroups[].connection.port
The TCP port to use for the SMTP/LMTP connection. The default is 25.
### transportgroups[].connection.proxyprotocol.server
The IP address of a outbound PROXY (haproxy) protocol server.
### transportgroups[].connection.proxyprotocol.port
The TCP port to use for the outbound PROXY (haproxy) protocol server. The default is 25.
### transportgroups[].connection.sourceip.ipv4
An array with one or more IPv4 [`addresses[]`](#config_smtpd:confval-addresses) IDs to use as local IP(s), or *false* to disable IPv4.
### transportgroups[].connection.sourceip.ipv6
An array with one or more IPv6 [`addresses[]`](#config_smtpd:confval-addresses) IDs to use as local IP(s), or *false* to disable IPv6.
### transportgroups[].connection.sourceip.random
Use a random source IP. If disabled it will use the first usable and non-suspended source IP from the ones configured. The default is *true*.
### transportgroups[].session.protocol
Only needed if *lmtp* is to be used. The default is *smtp*.
### transportgroups[].session.hostname
The HELO name to use when a hostname is not set for the [`addresses[]`](#config_smtpd:confval-addresses) chosen as source IP.
If neither the address or transport has a hostname it will fall back to use the system hostname.
### transportgroups[].session.extensions.chunking
Enable support for the CHUNKING extension. The default is *true*.
### transportgroups[].session.extensions.pipelining
Enable support for the PIPELINING extension. The default is *true*.
### transportgroups[].session.tls.mode
How to handle TLS for the connection. The default is to use optional TLS.
To do custom TLS verification per destination, use the pre-delivery script’s
[tls_X](https://docs.halon.io/hsl/archive/6.12-stable/predelivery.html#Try) parameters.
The script library contains an
[MTA-STS](https://github.com/halon-extras/mta-sts)
implementation.
* *optional*: Use opportunistic, unverified TLS (fall back to plain text).
* *require*: Require TLS, but don’t verify the peer.
* *dane*: Verify the peer using [DANE](https://halon.io/dane) (DNSSEC), it not supported fallback to *optional* TLS. The most secure option.
* *dane_fallback_require*: Prefer DANE, it not supported fallback to *require* TLS. Only makes sense for specific destinations.
* *dane_require*: Require DANE. Only makes sense for specific destinations.
### transportgroups[].session.tls.protocols
The TLS protocols that should be supported. The default is `!SSLv2,!SSLv3,!TLSv1,!TLSv1.1`.
### transportgroups[].session.tls.ciphers
The TLS ciphers that should be used (<=TLSv1.2). The default is `aNULL:-aNULL:HIGH:MEDIUM:+RC4:@STRENGTH`.
### transportgroups[].session.tls.ciphersuites
The TLS cipher suites that should be used (TLSv1.3).
### transportgroups[].session.tls.groups
The supported groups to offer for TLS key exchange. For TLS 1.2, this controls
the ECDHE curves, such as `X25519` or `secp256r1`. For TLS 1.3, this
controls the supported key-exchange groups, including hybrid post-quantum
groups such as `X25519MLKEM768`.
### transportgroups[].session.tls.implicit
Use implicit TLS (do not use STARTTLS). The default is *false*.
### transportgroups[].session.authentication.username
The username to use for password-based authentication (SASL). Normally used for sending via smarthosts. The default is to not authenticate.
### transportgroups[].session.authentication.password
See [`transportgroups[].session.authentication.username`](#config_smtpd:confval-transportgroups-.session.authentication.username).
### transportgroups[].retry.during
The time ( *?d?h?m?s* formatted time, eg. *5d*) during which an email will be re-delivered via the [defer queue](#queue).
Use [`transportgroups[].retry.intervals[]`](#config_smtpd:confval-transportgroups-.retry.intervals) to specify each delivery attempt.
This option is mutually exclusive with [`transportgroups[].retry.count`](#config_smtpd:confval-transportgroups-.retry.count).
Expired messages in the active queue will be bounced if the during period elapses while in the active queue.
> **Note**
>
> Be aware that this time is calculated against the actual received time of an email. If an email is held in the hold
> queue or suspended this time is still elapsing.
### transportgroups[].retry.count
The number of times to attempt re-delivery of an email via the [defer queue](#queue).
Determines the time in queue together with the [`transportgroups[].retry.intervals[]`](#config_smtpd:confval-transportgroups-.retry.intervals).
This option is mutually exclusive with [`transportgroups[].retry.during`](#config_smtpd:confval-transportgroups-.retry.during).
### transportgroups[].retry.intervals[]
An array of intervals (in seconds or *?d?h?m?s* formatted time, eg. *1h30m*) for how long the email should stay in the [defer queue](#queue)
before retry. If there are fewer intervals than [`transportgroups[].retry.count`](#config_smtpd:confval-transportgroups-.retry.count), the last interval
will be used for consecutive defers. As with most other transport parameters, this behavior can be
overridden via pre- and post-delivery [script](https://docs.halon.io/hsl/). By setting `notify` to *true* on an interval, a delayed
notification will be delivered to the sender at this time.
```yaml
transportgroups:
- id: mxes
retry:
count: 30
intervals:
- interval: 1m
- interval: 15m
- interval: 1h
notify: true
- interval: 2h
- interval: 3h
```
### transportgroups[].dsn
The Halon default is not send DSNs. If you override to send DSNs at the `transportgroups` level,
you can set this value to `null` at transport level, so that Halon will not send a DSN for specific transport(s).
### transportgroups[].dsn.transport
Enable bounces on delivery failures via the specified transport ID.
## Monitor directives
A [monitor](#api_monitor) interface allows you to access OpenMetric statistics as well as health check endpoints.
It needs to be configured in the [startup configuration](#config_startup) configuration.
### monitor.type
The type of the monitor. It must match the same type as configured in the [startup configuration](#config_startup).
The default is `http`.
### monitor.concurrency.total
The maximum total number of HTTP connections that the monitor interface will accept.
The default is 1000.
### monitor.timeout.idle
Number of seconds of inactivity before disconnecting a client.
This setting support time format syntax (eg. `1m`).
The default is *60* seconds.
> **Note**
>
> Halon uses an asynchronous model, and can support a much larger
> number of connected clients compared to servers that use a process- or thread-based model.
> Therefore, there is usually no reason to lower this setting.
### monitor.tls.certs.cert
Which [`pki.private[]`](#config_startup:confval-pki.private) to use for TLS. This option is required if [`monitor.type`](#config_startup:confval-monitor.type) is `https`.
### monitor.apikeys
List of allowed API keys for the monitoring HTTP server (sent in the *X-API-Key* header).
> **Warning**
>
> If no apikeys are specified. There is no authenticate required to query the monitoring endpoint.
### monitor.healthy.maxmessages.upper
### monitor.healthy.maxmessages.lower
If the system has more message loaded than the *.upper* limit, then system will be unhealty during it’s runtime until
message count drop below the *.lower* limit.
The reason for a upper and lower limit is to allow the system to recover before switching status.
### monitor.healthy.spoolfreebytes.upper
### monitor.healthy.spoolfreebytes.lower
If the system has less bytes free on all disks than the *.upper* limit, then system will be unhealty during it’s runtime until
the free space has increased above the *.lower* limit. The setting support byte format syntax (eg. `5GiB`).
The reason for a upper and lower limit is to allow the system to recover before switching status.
*smtpd.yaml*
```yaml
monitor:
healthy:
spoolfreebytes:
upper: 5GiB
lower: 10GiB
```
### monitor.healthy.spoolfreeinodes.upper
### monitor.healthy.spoolfreeinodes.lower
If the system has less inodes free on all disksthan the *.upper* limit, then system will be unhealty during it’s runtime until
the free inodes has increased above the *.lower* limit.
The reason for a upper and lower limit is to allow the system to recover before switching status.
### monitor.metrics
Publish custom metrics. These metrics can be updated from within HSL and the native C-API.
Names and labels must follow the syntax of OpenMetric.
*smtpd-app.yaml*
```yaml
monitor:
metrics:
- name: deliverytimes
type: histogram
buckets:
- 0.001
- 0.01
- 0.1
- 1.0
- 10
- name: authfail
type: counter
```
### monitor.metrics[].name
Name of the metric. This field is required.
### monitor.metrics[].type
Type of the metric (one of `counter`, `gauge` or `histogram`). The histogram type requires buckets. This field is required.
### monitor.metrics[].help
Help text of metric.
### monitor.metrics[].buckets[]
List of bucket values. Required for the `histogram` type.
## Queue directives
### queues.concurrency.total
The maximum total number of outbound SMTP connections that the
[queue subsystem](#queue) will make.
This should be the basis for the virtual queues you define.
Halon uses an asynchronous model, and can therefore support a much larger
number compared to servers that use a process- or thread-based model.
You must revise the value of [`environment.rlimit.nofile`](#config_startup:confval-environment.rlimit.nofile) so that
the server has enough file descriptor resources available.
The default is 10000.
*smtpd-app.yaml*
```yaml
queues:
concurrency:
total: 20000
```
*smtpd.yaml*
```yaml
environment:
rlimit:
nofile: 70000
```
### queues.maxmessages
The maximum number of messages in the
[queue subsystem](#queue) (all states). When this limit is exceeded no more messages will be accepted
over SMTP or HTTP. However internal messages will still be created. For more granular control use *quotas*.
The default is unlimited.
### queues.pooling.size
The maximum number of idle connections that the
[queue subsystem](#queue) will hold in its pooling
cache for connection reuse before eviction takes place.
You must revise the value of [`environment.rlimit.nofile`](#config_startup:confval-environment.rlimit.nofile) so that
the server has enough file descriptor resources available.
Unlimited is 0 (not recommended).
The default is 1000.
### queues.pooling.policy
The connection reuse policy. This can either be strict “remote” (based on both remoteip and remotemx matching), “remotemx” (based on only the remotemx matches, if available, otherwise remoteip) and “grouping” (based on only the grouping matches).
### queues.grouping
Queue groupings of either the recipientdomain or remotemx into a specific queue tree field called *grouping*. If not configured, the grouping field will be empty.
Groupings are matched in order.
### queues.grouping.default
The grouping value if not [`queues.grouping.groupings[]`](#config_smtpd:confval-queues.grouping.groupings) match. This can either be “recipientdomain” or “remotemx”. This property is required.
### queues.grouping.groupings[]
List of groupings.
### queues.grouping.groupings[].id
The grouping ID. In all API’s the grouping id should be prefix with `&`. This property is required.
### queues.grouping.groupings[].unique
If the grouping should be unique per *remoteip*. The only valid option (enum) is `remoteip`. This will append the `&grouping-name;` to the grouping name.
### queues.grouping.groupings[].remoteip[]
The values in the list can be IP addresses or subnets.
### queues.grouping.groupings[].remotemx[]
The values in the list can be domains, wild-card domains (`*.example.com`) or `/regex/` patterns.
### queues.grouping.groupings[].recipientdomain[]
The values in the list can be domains, wild-carded subdomains (`*.example.com`) or `/regex/` patterns.
Simple wild-cards are more efficient than full regexes.
> **Tip**
>
> The wildcard means *one or more levels of sub-domain*, so that it would match `abc.example.com` and `def.ghi.example.com` but not `example.com` itself.
> If you wish to include the organizational domain in your grouping, add it on a separate line.
### queues.pickup.prereaddata
Pre-fetch data for messages from disk (up to X bytes). The default is to not pre-fetch any data.
### queues.pickup.backpressure
Enable backpressure if there are more than amount of uncompleted post-delivery tasks.
## General directives
### postmaster.name
The name used in the *From* header of system generated email such as delivery reports.
### postmaster.address.localpart
The localpart of the email address used in the *From* header of system generated email such as delivery reports. The default is *postmaster*.
### postmaster.address.domain
The domain of the email address used in the *From* header of system generated email such as delivery reports. The default is the reporting MTA.
### addresses[]
This directive is used to define “named” IP addresses so that they can be referenced by
their ID from configuration and script. This is not only useful for giving IP addresses
descriptive IDs, but also since you can share script between multiple MTAs in a cluster
without having to reference specific IPs that only exists on one instance.
```yaml
addresses:
- id: "out1"
address: 198.51.100.5
hostname: smtp-out1.example.com
- id: "out1v6"
address: 2001:db8:85a3::8a2e:370:7334
hostname: smtp-out1.example.com
- id: "out2"
address: 198.51.100.6
hostname: smtp-out1.example.com
- id: "out2v6"
address: 2001:db8:85a3::8a2e:370:7335
hostname: smtp-out1.example.com
- id: "bulk"
address: 203.0.113.67
hostname: smtp-out2.example.com
```
They can then be referenced from for example script, like this pre-delivery snippet:
```ruby
$sourceip = ["out", "out2", "outv6", "out2v6"];
if (GetMetaData()["spam"] == "yes")
$sourceip = ["bulk"];
Try(["sourceip" => $sourceip]);
```
### addresses[].id
The ID of the IP address.
### addresses[].address
The IP address.
### addresses[].hostname
Will be used as HELO name if defined.
### addresses[].proxyprotocol.server
The IP address of a outbound PROXY (haproxy) protocol server.
### addresses[].proxyprotocol.port
The TCP port to use for the outbound PROXY (haproxy) protocol server. The default is 25.
## List directives
These directives are used to define “named” lists which can then be referenced by
prefixing their ID with “@” from the [queue policy](#config_policy), [queue suspend](#config_suspend)
and [queue delivery](#config_delivery) configurations.
Lists may also be created using the [C API](#plugins_native:lists-native-plugin).
### lists.tenantid.{id}[]
The values in the list can either be tenant IDs or `/regex/` patterns matching tenant IDs.
### lists.jobid.{id}[]
The values in the list can either be job IDs or `/regex/` patterns matching job IDs.
### lists.transportid.{id}[]
The values in the list can either be transport IDs or `/regex/` patterns.
### lists.localip.{id}[]
The values in the list can either be IP addresses or IP subnets (`192.168.0.0/24`).
### lists.grouping.{id}[]
The values in the list can either be groupings or `/regex/` patterns matching groupings.
### lists.remoteip.{id}[]
The values in the list can either be IP addresses or IP subnets (`192.168.0.0/24`).
### lists.remotemx.{id}[]
The values in the list can be domains, wild-card domains (`*.example.com`) or `/regex/` patterns.
### lists.recipientdomain.{id}[]
The values in the list can be domains, wild-card domains (`*.example.com`) or `/regex/` patterns.
## Resolver directives
### resolver.concurrency
The maximum number of concurrent (pending) DNS queries.
This should be set according to the DNS server used.
The default is 100.
### resolver.preference
The IP address family preference (A or AAAA), either `ipv4` or `ipv6` or `null` (`~`) for equal preference.
The default is `ipv6`.
### resolver.exclude[]
A list of types `ipv4`, `ipv6` (no IPv6 is available) or `tlsa` (if DANE is not used) to exclude as an performance optimization.
The default is not to skip any RR types.
### resolver.domain.cache.size
The number of domains to store in the in-memory LRU cache.
The default is to cache `10000` domains.
### resolver.domain.priority[]
A list of domains that will always have high priority when resolving. This list could contain your most common recipient domains.
### resolver.priority.dedup
If a dedup queue contains more than X items, promote the query/queue to high priority.
This promotes popular domains.
Set this property to 0 to disable promotions.
The default is to promote when there are `1000` deduped queries.
### resolver.cache.size
The number of DNS queries to store in the in-memory LRU cache.
The default is no caching.
### resolver.cache.ttl.min
Normalize the time-to-live values in the cache to have at least this value.
This setting support time format syntax (eg. `5m`).
### resolver.cache.ttl.max
Normalize the time-to-live values in the cache to have at most this value.
This setting support time format syntax (eg. `1h`).
### resolver.mx.exclude.ip[]
A list of IP addresses or networks that will be excluded from MX lookup results. This may be useful to prevent looping traffic (eg. MX resolving to 127.0.0.1).
### resolver.mx.exclude.mx[]
A list of domains or wild-card domains (`*.example.com`) that will be excluded from MX lookup results. This may be useful to prevent looping traffic (eg. MX resolving to localhost).
## Other directives
### plugins[]
Specify a reloadable configuration to a plugin based on it’s id.
```yaml
plugins:
- id: test
config:
myval: true
```
### plugins[].id
The ID of the plugin.
### plugins[].config
The reloadable configuration. This property may be either a string or an YAML object.
If this property is given as a string it must point to a YAML file on disk.
### pki.private[]
Array of private keys, possibly with X.509 certificates, for use with [`servers[]`](#config_smtpd:confval-servers) and [script](https://docs.halon.io/hsl/) functions such as
PKCS7, RSA, DKIM, client certificates, etc.
The `id` and `privatekey` properties are required, and `certificate` is optional.
The *privatekey* can however be defined in the [startup configuration](#config_startup)
for the same ID, in which case it is not required.
The private key and certificate should have either a `path` or `data` property.
```yaml
pki:
private:
- id: selfsigned
certificate:
data: |-
-----BEGIN CERTIFICATE-----
...
privatekey:
data: |-
-----BEGIN PRIVATE KEY-----
...
```
> **Note**
>
> For privilege separation reasons, it’s normally recommended to define the private keys in the
> [startup configuration](#config_startup) instead of here,
> as it is read before the privilege drop.
## Example
Below is an unrealistically simple example of a running configuration with a few transports.
The actual scripting is omitted, since it is normally packed by the tools.
```yaml
servers:
- id: default
transport: mailserver
phases:
mailfrom:
hook: ldaplookup
eod:
hook: inbound
concurrency:
total: 10000
ip: 10
- id: relay
transport: mx
phases:
mailfrom:
hook: relaylist
eod:
hook: outbound
transportgroups:
- id: default
transports:
- id: mx
session:
tls:
mode: dane
dsn:
transport: mx
retry:
count: 30
intervals:
- interval: 60
- interval: 300
- interval: 3600
notify: true
- interval: 7200
- id: mailserver
connection:
server: mail.example.com
port: 25
retry:
count: 30
intervals:
- interval: 10
- interval: 60
- interval: 3600
queues:
concurrency:
total: 10000
postmaster:
address: postmaster@example.com
name: Postmaster
```
# Suspend configuration
This file holds all static suspends that should be enforced when the system starts.
By default, *smtpd* loads it from */etc/halon/smtpd-suspend.yaml*.
It is described by, and can be validated with, the
[smtpd-suspend.schema.json](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/6.12-stable/smtpd-suspend.schema.json).
A suspend is a rule that prevents a message from being picked up from the [active queue](#queue_active:queue-active).
This file can be reloaded using the `halonctl queue suspend reload` command.
The overall concepts of suspends are documented in [active queue suspension](#queue_suspend:queue-suspend).
## Suspend directives
The suspend directive is an array of rules. Each item in the array represents one suspend.
If any of the suspends match, by any field combination it will be in effect for a specific queued message.
From a performance perspective it’s beneficial to use *string* and *\*.domain* over *regex* patterns.
> **Note**
>
> All fields are optional, meaning if an empty array item is found, everything is suspended.
>
> ```default
> suspends:
> - {}
> ```
### suspends[].tenantid
Supported types are *string*, */regex/* or a *@list* of those.
### suspends[].jobid
Supported types are *string*, */regex/* or a *@list* of those.
### suspends[].transportid
Supported types are *string*, */regex/* or a *@list* of those.
### suspends[].localip
Supported types are *ipv4*, *ipv4network*, *ipv6*, *ipv6network* or *@list* of those.
### suspends[].grouping
Supported types are *string*, */regex/* or a *@list* of those.
### suspends[].remoteip
Supported types are *ipv4*, *ipv4network*, *ipv6*, *ipv6network* or *@list* of those.
### suspends[].remotemx
Supported types are *domain*, *\*.domain*, */regex/* or a *@list* of those.
### suspends[].recipientdomain
Supported types are *domain*, *\*.example.com*, */regex/* or a *@list* of those.
### suspends[].tag
A tag (max 32 characters) defining the rule.
### suspends[].properties{}
Supported values are *string*, *number* or a *boolean*.
# Policy configuration
This file holds all static policies that should be enforced when the system starts.
By default, *smtpd* loads it from */etc/halon/smtpd-policy.yaml*.
It is described by, and can be validated with, the
[smtpd-policy.schema.json](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/6.12-stable/smtpd-policy.schema.json).
A policy is a rule that restricts how fast messages can be picked up from the [active queue](#queue_active:queue-active).
When a policy has been exceeded it will create a dynamic [active queue suspension](#queue_suspend:queue-suspend).
This file can be reloaded using the `halonctl queue policy reload` command.
The overall concepts of policies are documented in [queue pickup policies](#queue_policy:queue-policy).
## Policy directives
You can configure multiple policies. Each item in the array represents one [counter](#queue_policy:queue-policy).
### policies[].fields[]
The [policy fields](#queue_policy:queue-policy-fields) to use for the [counter](#queue_policy:queue-policy).
> **Note**
>
> [`policies[].fields[]`](#config_policy:confval-policies-.fields) can be omitted to enforce global limits for the entire system.
>
> ```yaml
> policies:
> - default:
> rate: 1000/60
> ```
### policies[].conditions[]
Conditions can be used to enforce limits for specific sub-queues.
You can only add conditions based on the policy fields that are included in [`policies[].fields[]`](#config_policy:confval-policies-.fields).
From a performance perspective it’s beneficial to use *string* and *\*.domain* over *regex* patterns.
### policies[].conditions[].if.tenantid
Supported types are *string*, */regex/* or a *@list* of those.
### policies[].conditions[].if.jobid
Supported types are *string*, */regex/* or a *@list* of those.
### policies[].conditions[].if.transportid
Supported types are *string*, */regex/* or a *@list* of those.
### policies[].conditions[].if.localip
Supported types are *ipv4*, *ipv4network*, *ipv6*, *ipv6network* or *@list* of those.
### policies[].conditions[].if.grouping
Supported types are *string*, *\*.domain*, */regex/* or a *@list* of those.
### policies[].conditions[].if.remoteip
Supported types are *ipv4*, *ipv4network*, *ipv6*, *ipv6network* or *@list* of those.
### policies[].conditions[].if.remotemx
Supported types are *domain*, *\*.domain*, */regex/* or a *@list* of those.
### policies[].conditions[].if.recipientdomain
Supported types are *domain*, *\*.domain*, */regex/* or a *@list* of those.
### policies[].conditions[].then.rate
The rate that should be enforced, specified as *x/y* where *x* is count and *y* is interval. It can also be specified as an object with a `value` property (same syntax as this property) and a `algorithm` property being one of `fixedwindow` or `tokenbucket`. The default is to inheirt the default rate algorithm.
### policies[].conditions[].then.concurrency
The concurrency that should be enforced.
### policies[].conditions[].then.connectinterval
The connect interval that should be enforced, specified in seconds. It can only be configured for a policy that has the `localip` and `grouping` [`policies[].fields[]`](#config_policy:confval-policies-.fields).
### policies[].conditions[].then.tag
The custom [tag](#queue_policy:queue-policy-properties) that should be applied.
### policies[].conditions[].then.properties{}
The custom [properties](#queue_policy:queue-policy-properties) that should be applied.
### policies[].conditions[].then.cluster
If clustering should be enabled. The default is *true* if [`cluster.address`](#config_startup:confval-cluster.address) is configured in the [startup configuration](#config_startup), otherwise *false*.
### policies[].default
The default limits that will be enforced for each sub-queue that did not match any `policies[].conditions`.
### policies[].default.rate
The rate that should be enforced, specified as *x/y* where *x* is count and *y* is interval. It can also be specified as an object with a `value` property (same syntax as this property) and a `algorithm` property being one of `fixedwindow` or `tokenbucket`. The default is to inheirt the default rate algorithm.
### policies[].default.concurrency
The concurrency that should be enforced.
### policies[].default.connectinterval
The connect interval that should be enforced, specified in seconds. It can only be configured for a policy that has the `localip` and `grouping` [`policies[].fields[]`](#config_policy:confval-policies-.fields).
### policies[].default.tag
The custom [tag](#queue_policy:queue-policy-properties) that should be applied.
### policies[].default.properties{}
The custom [properties](#queue_policy:queue-policy-properties) that should be applied.
### policies[].default.cluster
If clustering should be enabled. The default is *true* if [`cluster.address`](#config_startup:confval-cluster.address) is configured in the [startup configuration](#config_startup), otherwise *false*.
## Rate directives
### rate.algorithm
The rate algorithm to use. Can be either *fixedwindow* or *tokenbucket*. The key distinction between *fixedwindow* and *tokenbucket* is that *fixedwindow* permits bursting within the specified time interval. The default is *fixedwindow*.
# Delivery configuration
This file contains delivery configuration, including settings like pooling and retries.
Similar to the `smtpd-suspend.yaml` and `smtpd-policy.yaml` configuration files, it can be updated more independently.
These settings can also be modified using the HSL Pre-delivery hook. However,
unlike the Pre-delivery hook, this file supports conditions based on `grouping`, `remoteip`, and `remotemx`,
which are resolved after the Pre-delivery hook runs.
By default, *smtpd* loads it from */etc/halon/smtpd-delivery.yaml*.
It is described by, and can be validated with, the
[smtpd-delivery.schema.json](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/6.12-stable/smtpd-delivery.schema.json).
This file can be reloaded using the `halonctl queue delivery reload` command.
## Delivery directives
### delivery.conditions[]
Conditions can be used to apply settings.
From a performance perspective it’s beneficial to use *string* and *\*.domain* over *regex* patterns.
### delivery.conditions[].if.tenantid
Supported types are *string*, */regex/* or a *@list* of those.
### delivery.conditions[].if.jobid
Supported types are *string*, */regex/* or a *@list* of those.
### delivery.conditions[].if.transportid
Supported types are *string*, */regex/* or a *@list* of those.
### delivery.conditions[].if.localip
Supported types are *ipv4*, *ipv4network*, *ipv6*, *ipv6network* or *@list* of those.
### delivery.conditions[].if.grouping
Supported types are *string*, *\*.domain*, */regex/* or a *@list* of those.
### delivery.conditions[].if.remoteip
Supported types are *ipv4*, *ipv4network*, *ipv6*, *ipv6network* or *@list* of those.
### delivery.conditions[].if.remotemx
Supported types are *domain*, *\*.domain*, */regex/* or a *@list* of those.
### delivery.conditions[].if.recipientdomain
Supported types are *domain*, *\*.domain*, */regex/* or a *@list* of those.
### delivery.conditions[].then.tls.mode
The TLS mode that should be used. One of `disabled`, `optional`, `optional_verify`, `require`, `require_verify`, `dane`, `dane_require`, `dane_fallback_require`, `dane_fallback_require_verify`, or `trust`. The default is `optional`.
### delivery.conditions[].then.tls.protocols
The TLS protocols that should be supported. The default is `!SSLv2,!SSLv3,!TLSv1,!TLSv1.1`.
### delivery.conditions[].then.tls.ciphers
The TLS ciphers that should be used (<=TLSv1.2). The default is `aNULL:-aNULL:HIGH:MEDIUM:+RC4:@STRENGTH`.
### delivery.conditions[].then.tls.ciphersuites
The TLS cipher suites that should be used (TLSv1.3).
### delivery.conditions[].then.tls.groups
The supported groups to offer for TLS key exchange.
### delivery.conditions[].then.tls.trustanchor[]
The TLS trust anchors.
### delivery.conditions[].then.tls.trustanchor[].selector
The trust selector (integer).
### delivery.conditions[].then.tls.trustanchor[].mtype
The trust match type (integer).
### delivery.conditions[].then.tls.trustanchor[].data
The trust data (hex encoded).
### delivery.conditions[].then.pooling.enabled
If connection pooling should be enabled. The default is `false`.
### delivery.conditions[].then.pooling.evictable
If the connection is evictable from the cache due to the cache replacement policy (LRU) if the cache is full. The default is `true`.
### delivery.conditions[].then.pooling.timeout
The idle timeout (in seconds) before an unused connection is closed. This setting support time format syntax (eg. `5s`). The default is `5` seconds.
### delivery.conditions[].then.pooling.transactions
The transactions per connection (0 is unlimited). The default is *unlimited*.
### delivery.conditions[].then.timeout.connect
The connect timeout (in seconds).
This setting support time format syntax (eg. `30s`).
The default is `30` seconds.
### delivery.conditions[].then.timeout.smtp.{}
The connection timeout settings (in seconds) for one of below.
This setting support time format syntax (eg. `2m`).
The default is defined by the RFC.
* `BANNER`
* `HELO`
* `EHLO`
* `LHLO`
* `STARTTLS`
* `AUTH-CRAM-MD5`
* `AUTH-PLAIN`
* `AUTH-LOGIN`
* `AUTH-LOGIN-USER`
* `AUTH`
* `XCLIENT`
* `MAIL`
* `RCPT`
* `DATA`
* `EOD`
* `RSET`
* `NOOP`
* `QUIT`
### delivery.conditions[].then.retry.interval
See [`transportgroups[].retry.intervals[]`](#config_smtpd:confval-transportgroups-.retry.intervals).
### delivery.conditions[].then.extensions.chunking
Enable support for the CHUNKING extension. The default is `true`.
### delivery.conditions[].then.extensions.pipelining
Enable support for the PIPELINING extension. The default is `true`.
### delivery.default
The default settings that will apply if not match any `policies[].conditions`.
### delivery.default.tls.mode
The TLS mode that should be used. One of `disabled`, `optional`, `optional_verify`, `require`, `require_verify`, `dane`, `dane_require`, `dane_fallback_require`, `dane_fallback_require_verify`, or `trust`. The default is `optional`.
### delivery.default.tls.protocols
The TLS protocols that should be supported. The default is `!SSLv2,!SSLv3,!TLSv1,!TLSv1.1`.
### delivery.default.tls.ciphers
The TLS ciphers that should be used (<=TLSv1.2). The default is `aNULL:-aNULL:HIGH:MEDIUM:+RC4:@STRENGTH`.
### delivery.default.tls.ciphersuites
The TLS cipher suites that should be used (TLSv1.3).
### delivery.default.tls.groups
The supported groups to offer for TLS key exchange.
### delivery.default.pooling.enabled
If connection pooling should be enabled. The default is `false`.
### delivery.default.pooling.evictable
If the connection is evictable from the cache due to the cache replacement policy (LRU) if the cache is full. The default is `true`.
### delivery.default.pooling.timeout
The idle timeout (in seconds) before an unused connection is closed. This setting support time format syntax (eg. `5s`). The default is `5` seconds.
### delivery.default.pooling.transactions
The transactions per connection (0 is unlimited). The default is *unlimited*.
### delivery.default.timeout.connect
The connect timeout (in seconds).
This setting support time format syntax (eg. `30s`).
The default is `30` seconds.
### delivery.default.timeout.smtp.{}
The connection timeout settings (in seconds) for one of below.
This setting support time format syntax (eg. `2m`).
The default is defined by the RFC.
* `BANNER`
* `HELO`
* `EHLO`
* `LHLO`
* `STARTTLS`
* `AUTH-CRAM-MD5`
* `AUTH-PLAIN`
* `AUTH-LOGIN`
* `AUTH-LOGIN-USER`
* `AUTH`
* `XCLIENT`
* `MAIL`
* `RCPT`
* `DATA`
* `EOD`
* `RSET`
* `NOOP`
* `QUIT`
### delivery.default.retry.interval
See [`transportgroups[].retry.intervals[]`](#config_smtpd:confval-transportgroups-.retry.intervals).
### delivery.default.extensions.chunking
Enable support for the CHUNKING extension. The default is `true`.
### delivery.default.extensions.pipelining
Enable support for the PIPELINING extension. The default is `true`.
# Performance tuning
Halon will perform well with the default configuration.
However, in order to get the most performance out of Halon and the hardware it’s running on, there are some parameters that can be tuned.
Those are mainly about thread pool size for different event loops and tasks, which should be matched to your workload.
Determining appropriate numbers can be done either by analyzing a production server, or using simulation through various benchmarking tools such as `smtp-source` and `smtp-sink` from the Postfix package.
If the storage/disk is at 100% utilization, tuning thread pools might not yield better throughput as the disk performance may be a bottleneck.
Tuning them may still reduce the system load, however.
Another way of improving performance is to enable or disable various features.
> **Note**
>
> All configuration directives mentioned on this page are located in the configuration files,
> by default *smtpd.yaml*, *smtpd-app.yaml* and *smtpd-delivery.yaml*.
> Changes to thread tuning and startup configuration requires *smtpd* to be restarted.
>
> More information regarding the directives mentioned here can be found on the [Startup configuration](#config_startup),
> [Running configuration](#config_smtpd) and [Queueing subsystem](#queue_delivery:queue-delivery) pages in this manual.
>
> Documentation for `halontop` can be found on the [Command line interface](#cli) page.
## Thread tuning
In order to tune the number of threads, you have to test and evaluate the changes you make.
Too many threads may result in significant overhead in context switching, hence lowering the performance.
There should be a good sweet spot for each value. Not only changing the number of threads but also the thread priority
will affect the behavior and performance of the system.
When benchmarking, the following system tools can be used to monitor system utilization and Halon performance.
* `halontop` may be used to observe usage of thread pools and pending tasks
* `top -H` will show the thread CPU utilization, if a class of threads are running at high CPU, it may be worth increasing that thread type
* `atop 1` too see the disk utilization
### servers[].threads.event
A good starting point to find the number of event threads you should have is to look at the number of CPU cores your system has.
That number is a good starting point and depending on the workload best value might be either lower or higher than that.
### threads.scripts[].count
These are the actual operating-system threads that execute script code. A good starting point is around 32.
Observe the pending queue sizes and tune the value until the wait queues for script threads are stable and reasonably small.
If there are no pending scripts during benchmarking, consider lowering the value to conserve resources.
### threads.scripts[].concurrent
This configures how many scripts may be active concurrently on that thread pool. This is not the same as the number of script worker threads.
A script is considered active while it is running, or while it is waiting in the background for an asynchronous operation to complete.
For example, functions such as `dns_query`, `http_background` and `sleep` may cause a script to yield while waiting.
During that time, the script remains active, but it no longer occupies a script worker thread.
Tune this when scripts frequently perform asynchronous work, such as background HTTP requests, DNS lookups, or sleeps.
If it regularly reaches the configured limit while running threads are significantly below `thread.scripts[].count`,
the system may be limited by `thread.scripts[].concurrent` rather than by available worker threads.
In this context
* `thread.scripts[].count` controls how many scripts can execute code at the same time.
* `thread.scripts[].concurrent` controls how many scripts can be active, including scripts that are waiting in the background.
### queues.threads.pickup
If it seems like you have a bottleneck for outbound delivery (and verified that concurrency and rate isn’t saturated),
it could be that you need to increase the thread priority or add more threads working to find messages to deliver from the active queue.
In that case, try to increase the pickup threads from 1 to 2 and see if the performance improves.
### resolver.threads.event
If it seems like you have a bottleneck for DNS resolving, that could be shown as high CPU usage of the resolver thread (`dns/X`) or `resolver.running` (gauge) not being saturated.
In that case, try to increase the resolver threads from 1 to 2 to 4 and see if the performance improves.
### resolver.threads.ready
After DNS resolution completes, resolved queries are placed in the DNS ready queue for further processing before being added to the active queue.
This queue prevents additional processing from blocking the DNS resolver. This setting controls the number of threads processing items from the DNS ready queue.
The queue length can be monitored using the `resolver.ready` (gauge).
## Startup configuration
Changing those configuration parameters all have pros and cons, depending on whether you need that specific feature or not.
### environment.syslog.mask
If you’re using syslog for logging this might use a lot of CPU resources.
If this causes a bottleneck it might be a good idea to mask some of the log levels, especially if you’re using an external logging integration.
Please see the [`environment.syslog.mask`](#config_startup:confval-environment.syslog.mask) documentation for more information.
> **Note**
>
> We’ve seen issues with *systemd-journald* causing major performance issues and dropped log lines (due to its internal rate limiting), where other syslog services may be much faster and more suitable for high volume logs such as *rsyslog* and *syslog-ng*.
> If you install an alternative syslog service you still need to make sure that *systemd-journald* is not acting as an intermediate which could be the default behavior, make sure to completely bypass *systemd-journald*.
> If running *top* and *journald* is consuming a lot of CPU it a sign it’s in between or if the *systemd-journald* is mentioning “Suppressed” log lines.
### spool.fsync
The fsync setting will ensure that all data accepted by the MTA (on End-of-data) will be saved to disk before replying with the 250 OK to the sender.
This is to ensure that all accepted mail is transaction safe. However, depending on the types of email you’re sending and the risk you’re willing to take.
This option can be disabled either for all messages and/or enabled only for certain traffic.
Disabling this option will greatly reduce the number of IOPS that you need to perform hence in many cases improve the performance vastly.
### spool.loader.threads.wait
If you want the system to startup faster, the system can start accepting mail before it is full spooled in from disk.
This optimization comes at the risk of eg. quotas not being enforced correctly during startup. This will heavily increase
startup performance.
### environment.uuid.version
On various platforms the default version 1 is faster than version 4 while using the `uuidd` daemon.
If you’re unsure you may easily benchmark the uuid performance using hsh.
## Running configuration
Changing those configuration parameters all have pros and cons, depending on whether you need that specific feature or not.
### servers[].logging
You may choose to disable protocol logging or hook logging if you don’t need them for additional diagnostics.
### servers[].phases.connect.remoteptr
If you don’t use the remoteptr (FCrDNS), either for anti-spam or manual policies it might be a good idea to disable this feature.
### servers[].phases.data.multipart
If you don’t work with MIME multipart messages (except inspecting the top-level MIME - rfc822).
It’s recommended to disable this option as it will allow faster message reception. All MIME operations (MailMessage.findByName etc)
on the message will behave like the message didn’t contain any MIME parts.
### servers[].phases.data.fixheaders
This option will inspect the message and try to fix broken headers.
If a broken header is found a CRLF will be inserted hence making the broken header and the headers that follow (if any) part of the message body.
This behavior is for compliance with most of the other MTAs.
## Resolver subsystem
It may be useful to tune the various [resolver settings](#config_smtpd:resolver-settings) (enable the query cache and domain cache and exclude RR types not used).
If you are in control of the upstream resolver, there may be opportunities for tuning its performance as well (such as caching TTL for positive and negatives replies, as well as timeout and retry intervals).
> **Warning**
>
> All nameservers defined in your `/etc/resolv.conf` need to be able to answer to **all** queries and **zones**. You can not have servers which are only responsible for certain zones (eg. `.local`).
### resolver.concurrency
The resolver concurrency should match your upstream resolver concurrency. Our default is 100 which may be somewhat conservative.
### resolver.ednsbuffersize
If you are running a local upstream resolver, and you are on the same network with larger MTU (>1500, jumbo frames) then, it’s possible to increase the EDNS buffer size on both applications to reduce the number of queries falling back to TCP. This setting is done in the startup configuration (*smtpd.yaml*).
### resolv.conf
The resolver always uses the servers and settings configured in `/etc/resolv.conf`.
This file path cannot currently be overridden; so you need to use alternative approaches if you need custom DNS behavior compared to the OS itself.
This is partially by design to ensure a coherent DNS resolution across all builtin functionally and external `halon-extras` plugins.
Because DNS resolution in many `halon-extras` plugins are outside our control (in 3rd party libraries and dependencies) and rely on the builtin OS resolver behavior which uses the configuration of `/etc/resolv.conf`.
#### systemd
One way to solve this is to use `PrivateMounts` to mount a custom `/etc/halon/resolv.conf` when starting the process. You can achieve this by modifying the systemd configuration.
```console
# systemctl edit halon-smtpd
[Service]
PrivateMounts=yes
BindReadOnlyPaths=/etc/halon/resolv.conf:/etc/resolv.conf
# systemctl daemon-reload
# systemctl restart halon-smtpd
```
#### K8s
In Kubernetes you can configure custom DNS options for the *StatefulSet* using the [dnsConfig](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config) field in the K8s manifest.
A common option to change is to lower the `ndots` value to reduce the amount of DNS queries that needs to be done for external DNS lookups.
> **Warning**
>
> If you lower the `ndots` value you should always use the FQDN when referring to other internal
> services from inside the `smtpd` pod such as using `halon-clusterd.default.svc.cluster.local` as an example.
## Queueing subsystem
### queues.pickup.prereaddata
This setting allows the system to trade RAM usage for reduced disk latency by reading messages up
to the configured size fully into memory before passing the task to [`queues.threads.event`](#config_startup:confval-queues.threads.event).
This can be beneficial on a stressed system, especially when
* The message to be sent is not already in the kernel disk cache.
* The disk is under load, since prereading the data performs a more sequential disk read, which is often faster than interleaved reads from many messages during delivery.
### queues.pickup.backpressure
This setting applies backpressure to the queue pickup rate when there are too many messages in flight, meaning messages with pending post-delivery tasks.
Please note that this setting is not enforced as a strict limit. The number of pending tasks may temporarily exceed the configured value.
On a busy system, a limit of 3 to 10 times [`queues.concurrency.total`](#config_smtpd:confval-queues.concurrency.total) can be a reasonable starting point.
### pooling
If you have a lot of traffic, often to the same hosts, it can be useful to enable [pooling](#queue_delivery:queue-delivery).
This will allow the MTA to reuse already established and idle connections when sending messages.
## Memory tuning
If you notice that memory usage (RSS) increases over time without any correlation to the message queue size or experience out-of-memory issues,
it is recommended to configure the environment variable
[MALLOC_ARENA_MAX](https://www.gnu.org/software/libc/manual/html_node/Malloc-Tunable-Parameters.html#index-M_005fARENA_005fMAX).
You can achieve this by modifying the systemd configuration.
```console
# systemctl edit halon-smtpd
[Service]
Environment=MALLOC_ARENA_MAX=2
# systemctl daemon-reload
# systemctl restart halon-smtpd
```
## Disk tuning
Disk performance is important.
In our experience, `ext4` has performed better than `xfs` for our workload (files on disk representing a short-lived transaction).
However, when using `spool.fsync`, most reads and writes will not touch the disk during normal operation.
This behavior can be observed with tools such as `iotop`.
If the disk cache cannot be used, for example after a reboot,
or if the cache is dropped due to high memory usage.
A high-IOPS, low-latency disk becomes important for maintaining high throughput.
If you experience lower performance in these situations, the disk may be a limiting factor.
In order to test the worst-case disk performance you can manually clear the disk cache.
```console
# sync
# echo 3 | tee /proc/sys/vm/drop_caches
```
# Queuing subsystem
Halon provides control and flexibility on how messages are handled and delivered,
according to policies that you define. The MTA queue is central to this.
The default behavior of an MTA is to spool messages, which are being received, to disk,
then pick them up and deliver them by using the queuing subsystem. This allows the MTA to
accept messages even if the next hop is unavailable, or if a message has multiple
recipients on different next hop destinations.
This figure illustrates the typical states and movements inside the queue.
```mermaid
flowchart LR
smtp{{SMTP}} --> spool[(Spool)]
http{{HTTP}} --> spool
api{{API}} --> spool
start((Startup)) --> spool
spool --> spoolin(Spool-in)
spoolin --> pre(Pre-delivery hook)
spoolin -->|if now < next retry|defer[(Defer queue)]
spoolin -->|if message on hold|hold[(Hold queue)]
pre <--> defer
pre <--> hold
pre --> dns(DNS resolving)
dns -->|DNS error|post(Post-delivery hook)
dns --> active[(Active queue)]
active-->|SMTP delivery attempt|post
post --> defer
post --> hold
post --> done{{Message Done}}
```
Each message in the queue can be in one of three states:
Active
: Messages that are not on hold, and are scheduled for delivery now (or in the past).
Messages that are put in [queue()](https://docs.halon.io/hsl/eodonce.html#EODMailMessage.queue) for delivery end up directly in the
active queue (unless the “delay” option is used). Messages are also moved automatically
from the defer queue into the active queue on their scheduled time of delivery.
The active queue is the by far most advanced part of the queuing subsystem, and contains
many sub-states. This stems from the fact that concurrency, rate and connectinterval can be limited based on
many dynamic parameters, essentially creating a virtually unlimited number of sub-queues.
Defer
: Messages that are not on hold, and are scheduled for delivery in the future. The default
behavior, for every message that fails delivery (with a non-permanent error), is to be put
in the defer queue; scheduled to be re-tried sometime in the future. It is a queue in the
sense that messages are ordered by their scheduled delivery time.
Hold
: Messages that are on permanent hold.
This is the most simple, and typically least used, state.
It is not a queue; rather a collection of messages.
It can be used to, for example, implement a spam/virus quarantine.
Messages can be put in hold list directly during the reception of a
message, using the end-of-DATA script [queue()](https://docs.halon.io/hsl/eodonce.html#EODMailMessage.queue) function’s “hold” option.
* [Active queue](#queue_active)
* [Active queue pickup](#queue_active:active-queue-pickup)
* [Delivery settings](#queue_delivery)
* [Queue policies](#queue_policy)
* [Available fields](#queue_policy:available-fields)
* [Loading policy](#queue_policy:loading-policy)
* [Policy counter thresholds](#queue_policy:policy-counter-thresholds)
* [Policy conditions](#queue_policy:policy-conditions)
* [Policy properties](#queue_policy:policy-properties)
* [Backoff policy](#queue_policy:backoff-policy)
* [Policies with *uniqueip*](#queue_policy:policies-with-uniqueip)
* [Policy precedence](#queue_policy:policy-precedence)
* [Policy counter groups](#queue_policy:policy-counter-groups)
* [Cluster-aware policy](#queue_cluster)
* [Example architecture](#queue_cluster:example-architecture)
* [Delivery Orchestrator: `clusterd`](#queue_cluster:delivery-orchestrator-clusterd)
* [Writing policy for clusters](#queue_cluster:writing-policy-for-clusters)
* [Advanced usage: selective non-clustered properties](#queue_cluster:advanced-usage-selective-non-clustered-properties)
* [Observing clustered policy in operation](#queue_cluster:observing-clustered-policy-in-operation)
* [Moving messages between instances](#queue_cluster:moving-messages-between-instances)
* [Message suspension](#queue_suspend)
* [Excluding IPs from pool](#queue_suspend:excluding-ips-from-pool)
* [Changing message states](#queue_changing)
* [Messages on hold](#queue_changing:messages-on-hold)
* [Spool startup](#queue_load)
* [On-disk format](#queue_load:on-disk-format)
* [Queue statistics via CLI/API](#queue_cli)
# Active queue
Message delivery goes through five stages:
Pre-delivery script
: The [pre-delivery script](https://docs.halon.io/hsl/archive/master/predelivery.html) is executed (if it exists).
The script allows the administrator to implement per-attempt logic, such as dynamic routing.
DNS
: All necessary domain name information is resolved.
If that succeeds, the message is put in the active queue.
If not, it skips directly to the post-delivery script.
Active queue
: The message remains in the active queue until allowed by the active queue’s pickup policies
(that control concurrency, rate, connectinterval, etc).
The pickup policies define the virtually unlimited number of sub-queues,
and will be described later in detail.
Delivery
: A delivery attempt (SMTP or LMTP) is made.
Post-delivery script
: Regardless of the outcome of the delivery attempt, the [post-delivery script](https://docs.halon.io/hsl/archive/master/postdelivery.html) is executed
(if it exists). It can be used by the administrator to override the default logic
or to implement logging.
## Active queue pickup
The active queue pickup subsystem determines when a message should be picked up,
usually based on *concurrency*, *rate* or *connectinterval* settings on properties
such as local IP or destination. The queue pickup takes into account [message suspensions](#queue_suspend) that are in force.
The [Policy configuration](#config_policy) file defines combinations of [policy fields](#queue_policy:queue-policy-fields), forming virtual sub-queues for messages.
This is useful for separating email of different classes, so that one class of email
that is stuck or moving slowly does not block others.
Those sub-queues are created based on any properties; pre-defined ones like
recipient domain, or custom fields that can be populated from [script](https://docs.halon.io/hsl/).
For example, consider the case where the system’s total concurrency is set to 20 000,
and the system has two local IP addresses that can be used as source IPs when sending email.
With a pickup policy limiting the concurrency to 10 000 connections per local IP,
we can be certain that even if traffic from one of the IPs jams up,
traffic from the other IP will not be disturbed.
This concept can be extended to, for example, customers in a multi-tenant system
(making sure that abuse from one customer does not jam the queue for others)
or recipient domains/MXs
(so that one slow destination does not jam the queue for other destinations).
Finally, combinations of those can be created.
As explained above the queue is not a FIFO (first-in first-out) type of queue,
as messages are divided into virtual queue and delivered in the fastest possible way
without exceeding the queue policies (of concurrency and rates). However there is a concept
of priority messages (0 - normal, 1 - high), these will be considered candidates for pickup/delivery
before any other messages. They also have precedence when handled by the DNS resolver and pre- and post-delivery threads.
However given the explained concept there are no guarantees that they will
be delivered before a low priority message in case of suspending rules (exceeding concurrency or rates).
Also within each queue it employs FIFO.
That guarantees that no message should be stuck in the queue because of unfair treatment.
Below is an illustration of how the queue looks like.
```mermaid
flowchart LR
A[Active Queue] --> te0(tenantid#1)
te0 --> j1(jobid#1)
A --> te1(cust1)
te1 --> j2(sendout1)
j1 --> t1(transportid#1)
t1 --> l1(localip#1)
t1 --> l2(localip#2)
l1 --> b1[*] --> g1(grouping#1)
l2 --> b1
g1 --> r1(remoteip#1)
r1 --> m1(remotemx#1)
m1 --> d1(recipientdomain#1)
d1 --> q1[Message Queue FIFO]
j2 --> t2(transactional)
t2 --> l3(1.2.3.4)
t2 --> l4(1.2.3.5)
l3 --> b2[*] --> g2(&yahoo)
l4 --> b2
g2 --> r2(5.6.7.8)
r2 --> m2(mta1.am0.yahoodns.net)
m2 --> d2(yahoo.com)
d2 --> q2[Message Queue FIFO]
b2 --> g3(&google)
g3 --> r4(9.8.7.6)
r4 --> m3(alt2.gmail-smtp-in.l.google.com)
m3 --> d4(google.com)
d4 --> q3[Message Queue FIFO]
m3 --> d5(gmail.com)
d5 --> q4[Message Queue FIFO]
A --> te3(cust2)
te3 --> j3(sendout2)
j3 --> t3(bulk)
t3 --> l5(1.2.3.6)
l5 --> b3[*] --> g4(&yahoo)
g4 --> r3(5.6.7.8)
r3 --> m4(mta1.am0.yahoodns.net)
m4 --> d3(yahoo.com)
d3 --> q5[Message Queue FIFO]
```
# Delivery settings
The active queue delivery configuration can be used
to set parameters such as timeouts and connection pooling
based on fields that are only available in the active queue state,
such as *remotemx* and *remoteip*.
Because they might be generated by an external system,
or managed separately from the rest of the configuration,
they are loaded from a separate file.
The default
[start configuration](#config_startup)’s
[`environment.deliveryconf`](#config_startup:confval-environment.deliveryconf) directive
loads it from */etc/halon/smtpd-delivery.yaml*.
It is described by, and can be validated with, the
[smtpd-delivery.schema.json](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/6.12-stable/smtpd-delivery.schema.json)
[JSON schema](https://docs.halon.io/json-schemas/6.12-stable/) (included in our [Visual Studio Code integration](#vscode)).
[Lists](#config_smtpd:config-list) may be used to reference and match multiple values at the same time.
The following properties can be changed in the delivery settings file.
* TLS mode
* Pooling options
* Timeouts
* Retry (intervals and count, but not during)
The example below enable connection pooling, with priority for MXs on “example.com”:
```yaml
delivery:
conditions:
- if:
remotemx: "*.example.com"
then:
pooling:
enabled: true
timeout: 300
evictable: false
transactions: 1000
tls:
mode: "optional"
- if:
remoteip: 192.0.2.1
then:
timeout:
connect: 120
smtp:
MAIL: 900
EOD: 900
default:
pooling:
enabled: true
```
# Queue policies
Halon provides powerful policy features to control the flow of emails from the queue to recipients.
*Concurrency*, *rate* and *connectinterval* limits are measured with *counters*,
which can have one or multiple *fields* that define a unique *entry*.
## Available fields
`tenantid`
: A single value free-text field that can be used for any purpose. Email service providers (ESPs) typically have many customers (tenants) using the same system.
This field enables you to create policy on a per-tenant basis when a message is queued, to handle responses relating to the tenant’s reputation.
One way is to set the `tenantid` to the header *From* domain when [queuing the message](https://docs.halon.io/hsl/eod.html#EODMailMessage.queue) in HSL.
`jobid`
: A single value free-text field that can be used for any purpose, for example to identify the current campaign for bulk mailing, or message stream for transactional mailing.
One way is to use the value of an incoming email header such as *X-Job*.
This enables you to create policy to manage traffic flow on a per-job basis.
`transportid`
: A single value referring to a [`transportgroups[].transports[]`](#config_smtpd:confval-transportgroups-.transports), similar to “binding” in other systems.
A `transportid` can map to one or more `localip`(s), or to other settings, such as specific custom destinations.
`localip`
: This is a list, so that an email can be queued with multiple alternatives for source IP.
The same source IP may be specified multiple times to distribute the traffic according to its ratio.
`grouping`
: A single value used for “rolling up” queued emails. It’s set after DNS resolving, it may either default to empty, *remotemx* or *recipientdomain*.
Any grouping configured using the [`queues.grouping`](#config_smtpd:confval-queues.grouping) setting will apply and fill out this field (the grouping id will be prefixed with `&` throughout the queue).
`remoteip`
: This single value is known after DNS resolving.
In some cases it’s preferable to build policies on the `grouping` field instead together with the [`queues.grouping.groupings[].remoteip[]`](#config_smtpd:confval-queues.grouping.groupings-.remoteip) setting.
`remotemx`
: This single value is known after DNS resolving.
In some cases it’s preferable to build policies on the `grouping` field instead together with the [`queues.grouping.groupings[].remotemx[]`](#config_smtpd:confval-queues.grouping.groupings-.remotemx) setting.
`recipientdomain`
: This single value is a basic property of the message `RCPT TO` address.
In some cases it’s preferable to build policies on the `grouping` field instead together with the [`queues.grouping.groupings[].recipientdomain[]`](#config_smtpd:confval-queues.grouping.groupings-.recipientdomain) setting.
## Loading policy
The default
[start configuration](#config_startup)’s
[`environment.policyconf`](#config_startup:confval-environment.policyconf) directive
loads it from */etc/halon/smtpd-policy.yaml*.
It is described by, and can be validated with, the
[smtpd-policy.schema.json](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/6.12-stable/smtpd-policy.schema.json)
[JSON schema](https://docs.halon.io/json-schemas/6.12-stable/) (included in our [Visual Studio Code integration](#vscode)).
If reloading the [`environment.policyconf`](#config_startup:confval-environment.policyconf) configuration during runtime, active suspensions based on (now) removed or previous exceeded
rate, concurrency or connectinterval limits are not automatically removed (as a side effect of the reload)
instead the suspension is removed when the next message for that rate, concurrency or connectinterval is allow to be sent.
If adding a new counter to the configuration during a reload, the counter will only count new messages (and not those being sent).
In addition to the configuration file on disk, policy conditions can be added on the fly over the
[Protocol Buffer](https://docs.halon.io/protobuf-schemas/6.12-stable/) API’s `PolicyConditionAddRequest` function, [command line interface](#cli),
[web administration](#web),
as well as from the pre- and [post-delivery script](https://docs.halon.io/hsl/archive/master/postdelivery.html).
## Policy counter thresholds
Thresholds for concurrency limit the number of emails in the delivery state.
Thresholds for rate limit the number of emails, *X*, passing through the delivery state
over a given time interval, *Y*, specified in seconds, as *X/Y*.
Thresholds for connectinterval limit the number of connections to be open for a specific destination.
The very simplistic example from above (with two local IPs) can be described using the following YAML pickup policy configuration:
```yaml
policies:
- fields:
- localip
default:
concurrency: 10000
```
Each time an email is picked up from the active queue, the “localip” concurrency counter entry with that email’s
source IP is incremented. When the delivery attempt is done, the same counter entry is decremented.
If 10 000 email for the same source IP is being delivered at the same time, the default threshold will be exceeded,
and the suspension list will be populated with an entry saying that any email with that source IP should not be picked up.
Note that all configured queue pickup policies (counters) are taken into consideration when an email is picked up from the active queue,
and the lowest allowed rate, concurrency and connectinterval applies.
And that it’s possible to leave out the default value for a counter (so that only a specific condition is applied).
### Policy conditions
Different thresholds can be set by using conditions with the desired field values.
Conditions are evaluated first-to-last, with the first matching threshold winning.
If no matches are found for specific conditions, the configured defaults will apply.
Consequently, if a threshold in a more broadly matched condition is placed before
a more specific match in the configuration, the specific condition may never apply
because the more general condition will be encountered first and take precedence.
Thus, when configuring policies care should be taken to ensure that the most specific
conditions are listed first, and the most general conditions are listed last.
The above procedure is repeated individually for all configured thresholds. If a given matched
condition specifies a rate but not a concurrency for example, a concurrency may still be
set by a different matching condition. If this is not desirable, a
[null value](#queue_policy:queue-policy-properties) must be set so that a match is found, but nothing happens with it.
The example below limits the concurrency based on a combination of source IP and destination domain, with an override
for the domain “halon.io”:
Per every field in a `if` condition (eg. `recipientdomain`) multiple values (eg. domains) may be given as an array,
and matched as or-conditions.
[Lists](#config_smtpd:config-list) may be used to reference and match multiple values at the same time.
```yaml
policies:
- fields:
- localip
- recipientdomain
conditions:
- if:
recipientdomain:
- halon.io
- halon.se
then:
concurrency: 2
default:
concurrency: 5
```
The above policy will be exceeded if two emails are being delivered to the recipient domain “halon.io” from the same source IP.
Policy outcomes may be tested using the [queue policy command line tool](#cli_queue:cli-queue-policy) to verify the matched policies
are what you expect (include all relevant field values):
```console
$ halonctl queue policy match --localip 1.1.1.1 --transportid foo --grouping "&foo" --json
```
### Policy properties
When a policy condition (`if`) is matched, the policies given in the `then` are applied.
There are three counter thresholds that can be applied; the `concurrency`, the `rate` and the `connectinterval` policies, that will affect the message delivery.
If you don’t want apply specify specific `concurrency`, `rate` or `connectinterval`, nor the `default` matching, you can configure them as *null*.
There is also a `tag` property which can be used to identity which condition were matched.
In addition custom properties may by specified in a `properties` object as key and value, these custom properties
are available eg. in the post-delivery scripting hook.
```yaml
policies:
- fields:
- localip
- recipientdomain
conditions:
- if:
recipientdomain: halon.io
then:
concurrency: 2
rate: 1/60
tag: this_rule
properties:
backoff-concurrency: 1 # Slow down messages when in backoff
backoff-retry-intervals: 600,600,1200,1200,1800
backoff-rate: 1/10
backoff-ttl: 3600
backoff-disableable: true
backoff-suspendable: true
foo: bar # Can set other custom properties too
default:
concurrency: 5
```
## Backoff policy
Mailbox providers can send SMTP diagnostic responses (i.e. 4xx or 5xx codes) that indicate message streams are being sent too quickly.
The [halon-extras-backoff](https://github.com/halon-extras/backoff) package provides functions that can efficiently detect these responses and act on them.
When regular expressions in the `backoff.csv` file match, the system will apply specific backoff policy settings.
This allows for fine-grained control over behavior such as how quickly deliveries are attempted.
The scope of the backoff is defined by the *fields* passed to the `enable_backoff()` function. Usually these are applied to (*localip* + *grouping*) combinations,
but they could also be applied to other field combinations using *tenantid* or *jobid*, to slow down specific senders or message streams, while keeping others unaffected.
The backoff settings in [Policy properties](#queue_policy:queue-policy-properties) can set a specific *backoff-rate*, *backoff-concurrency*, *backoff-connectinterval*,
and *backoff-retryintervals* to override the normal settings.
The time to live *backoff-ttl* setting makes the backoff auto-expire after a number of seconds.
The *backoff-disableable* setting allows the backoff to be disabled by a successful delivery occurring. It is usually set *true*.
The *backoff-suspendable* setting allows the applicable sub-queue to be fully suspended by backoff patterns that have the action `suspend=..`,
and is usually set *true*.
Other features of the *backoff.csv* format:
* Rules can have a `tag`, this is a human-readable name that can be seen when viewing active backoffs in the web UI and CLI, to identify which condition was matched
* Rules can be scoped to a mailbox provider, by using specific *grouping* values (such as `&google`)
* The rule scope can be limited to a specific SMTP conversation state (e.g. `EOD`)
* Rules can be written to require more than one matching response in a time period to trigger, using `events=n/m`
* Rules can completely suspend a sub-queue, using the action `suspend=..` (if *backoff-suspendable* is set to *true*)
The backoff rules are evaluated in the order they are defined in the CSV file, and the first matching rule will be applied.
For a full description of the CSV backoff rule format, backoff policy settings, and how to use the HSL functions, see the repository [README](https://github.com/halon-extras/backoff).
## Policies with *uniqueip*
The [`queues.grouping.groupings[].unique`](#config_smtpd:confval-queues.grouping.groupings-.unique) setting is used for certain mailbox providers where policy should be applied separately per **unique remote IP address**.
The `if` conditions used to match these groupings in the *-policy*, *-delivery*, and *-suspend* files need to be written as regexes.
Each regex should begin with `/^` and end with `;.*/` to fully match the expanded policy id, which will be of the form `&grouping-name;`, for example:
*smtpd-app.yaml*
```yaml
queues:
grouping:
groupings:
- id: pphosted
unique: remoteip
remotemx:
- "*.pphosted.com"
```
*smtpd-policy.yaml*
```yaml
policies:
fields:
conditions:
- if:
grouping:
- "/^&pphosted;.*/"
then:
concurrency: 15
```
*smtpd-delivery.yaml*
```yaml
delivery:
conditions:
- if:
grouping:
- "/^&pphosted;.*/"
then:
pooling:
transactions: 300
```
## Policy precedence
Policies which are created by Web, CLI or API calls while the system is running are known as *dynamic* policies. These have have a time-to-live (TTL)
and are reset if the service is restarted. Because there are now several sources of dynamic policies, these are the policy types,
and the precedence when individual conditions are evaluated by the system:
> * Dynamic user-created policy (highest precedence)
> * Dynamic backoff policy
> * Dynamic warmup policy
> * Static policy
## Policy counter groups
> **Note**
>
> Please note that it’s recommended to use the `grouping` field described in [Queue policies](#queue_policy) together with the [`queues.grouping`](#config_smtpd:confval-queues.grouping) setting if you don’t need per policy groupings.
Counters can be aggregated based on wild-card, subnets or regular expression matching, so that different
field values count against the same entry. Groups are given IDs,
and conditions are matched against the grouped entry by prefixing with “#”.
The example below has two counters, with multiple fields per counter.
One limits both rate and concurrency based on destination MX (with rollup for Google G-suite) in combination with source IP.
The other also limits the concurrency per source IP, but destination IP instead of MX,
and only enforces a threshold for emails to recipient domains with a Microsoft Outlook MX.
```yaml
policies:
- fields:
- localip
- remotemx:
gsuite:
- '*.google.com'
- '*.googlemail.com'
- '*.smtp.goog'
conditions:
- if:
remotemx: '#gsuite'
then:
concurrency: 10
rate: 50
default:
concurrency: 5
rate: 10
- fields:
- localip
- remotemx:
o365:
- '*.protection.outlook.com'
- remoteip
conditions:
- if:
remotemx: '#o365'
then:
concurrency: 10
rate: 30
```
# Cluster-aware policy
## Example architecture
```mermaid
flowchart LR
GEN(["Message
Generation"]) --> LB
LB["Load
balancer"] --> H1
LB--> H2
LB --> H3
H1[Halon smtpd 1] --> C
H2[Halon smtpd 2] --> C
H3[Halon smtpd n] --> C
HAP1[HAProxy 1] --> |SMTP| MBP
HAP2[HAProxy 2] --> |SMTP| MBP
C(( )) --> HAP1
C --> HAP2
MBP(["Network
egress"])
```
Generated messages are network load-balanced across all Halon instances, which are configured identically.
The delivery (*localip*) addresses are located on the proxies; any Halon instance can choose any *localip*.
The HAProxy layer can use `keepalived` or similar, to provide resilience in the event of proxy host outage.
A basic approach is to statically divide the overall concurrency and rates across Halon instances.
For Kubernetes deployments, a plugin *halon-extras-k8s-policydivide* is available which calls [`HalonMTA_queue_policy_divide()`](#plugins_native:c.HalonMTA_queue_policy_divide) to adjust the traffic shaping.
This is acceptable for mailbox providers that allow concurrency greater than your number of instances, but some mailbox providers expect lower concurrency.
To solve for all cases, a different approach to traffic shaping is needed for equitable sharing of connections. This is provided by the `clusterd` service.
## Delivery Orchestrator: `clusterd`
`clusterd` works in dynamic scaling situations with Kubernetes, as well as static scaling with virtual machines.
```mermaid
flowchart LR
H1[Halon smtpd 1]
H2[Halon smtpd 2]
H3[Halon smtpd n]
CLUSTERD[clusterd] <--> H1
CLUSTERD <--> H2
CLUSTERD <--> H3
```
It allocates effective policy values to instances:
> [`policies[].conditions[].then.rate`](#config_policy:confval-policies-.conditions-.then.rate)
> : Rates are shared across the cluster, depending on the chosen algorithm; more on this below.
>
> [`policies[].conditions[].then.concurrency`](#config_policy:confval-policies-.conditions-.then.concurrency)
> : For each (*localip* + *grouping*) combination, outbound connections are allocated to instances in the cluster, ensuring fair rotation of concurrent connections, even for low concurrency destinations.
> It ensures each instance with messages to deliver will receive a turn.
>
> [`policies[].conditions[].then.connectinterval`](#config_policy:confval-policies-.conditions-.then.connectinterval)
> : For each (*localip* + *grouping*) combination, set a minimum time interval between connections, shared across the cluster.
`clusterd` registers connected Halon instances, counts them and announces the total number back to each instance.
See [Clustering directives](#config_startup:clustering-directives) for the required *smtpd* startup configuration.
To configure the `clusterd` service itself, see [Delivery Orchestrator](/clusterd/).
If a Halon instance loses connection to `clusterd`, it will revert to division of slots (min 1 connection per node) based on the last number of known hosts.
It will automatically try to reconnect and re-establish dynamic sharing.
The service supports TLS authentication/verification of both the server and client, over TCP.
## Writing policy for clusters
A system with `clusterd` active has policies clustered by default.
**Concurrent connections** are granted to instances in the cluster.
The overall cluster follows the *concurrency* properties for (*localip + grouping*) combinations in the *smtpd-policy.yaml* [`policies[].fields[]`](#config_policy:confval-policies-.fields).
Because connections are opened, closed and pooled between specific `localip` and `grouping`, these fields must be used when writing cluster-aware concurrency policy.
*smtpd-app.yaml*
```yaml
groupings:
- id: orange
remotemx:
- "smtp-in.orange.fr"
```
*smtpd-policy.yaml*
```yaml
rate:
algorithm: tokenbucket
policies:
- fields:
- jobid
- fields:
- localip
- fields:
- tenantid
- fields:
- tenantid
- grouping
- fields: # cluster-wide policies here
- localip
- grouping
default:
concurrency: 20
conditions:
if:
grouping: &orange
then:
concurrency: 2 # Cluster-wide rate for all localip, see https://postmaster.orange.fr/
```
**Rates** for the cluster can be set on *any* field condition, because rates are always divisible.
How they are divided depends on your chosen [`rate.algorithm`](#config_policy:confval-rate.algorithm) setting.
> Algorithm `tokenbucket` (default) gives smooth traffic flow over the interval by scaling the denominator, i.e.
> `tokens / (interval * number_of_hosts)`
>
> Algorithm `fixedwindow` gives traffic bursts at the start of each interval, by allocating the tokens between hosts, with each getting at least 1, i.e.
> `max(1, tokens / number_of_hosts) / interval`
For fair rotation of connections between your instances, set `delivery.pooling.timeout` to small values such as 5 to 10 seconds, and `delivery.pooling.transactions` to 100 or less.
If you wish to disable rotation of connections on an entire smtpd instance, set [`cluster.policy.sharedconcurrency`](#config_startup:confval-cluster.policy.sharedconcurrency) to `false`.
This will give basic “divide by n” concurrency, with at least 1 connection per instance.
### Advanced usage: selective non-clustered properties
Rules can selectively disengage clustering, for example to give more throughput to time-critical message streams.
*smtpd-policy.yaml*
```yaml
conditions:
if:
localip: 1.2.3.4
grouping: &yahoo
then:
concurrency: 5
cluster: false # each instance will get 5 connections
default:
rate: 5/1 # default rate applies cluster-wide
```
The `cluster: false` property applies to all properties in `then` scope.
To create policy that has some properties clustered, and others non-clustered, create two `if` clauses:
```yaml
conditions:
if:
localip: 1.2.3.4
grouping: &yahoo
then:
concurrency: 5
cluster: false # affects properties in “then” scope
if:
localip: 1.2.3.4
grouping: &yahoo
then:
rate: 10/1 # put (clustered) rate property in a separate if clause
default:
rate: 5/1 # default rate applies cluster-wide
```
## Observing clustered policy in operation
The [web user interface](https://docs.halon.io/web/) Delivery Insights view shows cluster behavior.
For each policy setting, it shows:
* the cluster’s concurrency and rate
* the effective concurrency and rate in use on this instance.
For example, a policy rate of `1/10` (one message every 10 seconds) with the *tokenbucket* algorithm, will be divided by the number of instances in the cluster
giving an effective rate on a three-instance cluster of `1/30`.
The [halontop](#cli_stats:halontop) command has a panel showing the cluster status and number of hosts found.
## Moving messages between instances
Messages can be moved from the current MTA to another, using the `hqfmove` command-line utility. See also [Moving the queue files with hqfmove](#cli_queue:cli-hqfmove).
Before moving messages, either:
> - shut down the `smtpd` process, or
> - unload specific messages using `halonctl queue unload` and move messages from the [`spool.path`](#config_startup:confval-spool.path) folder to a separate folder, so new messages arriving are not mixed with the messages to transfer.
Run the command on the source MTA, specifying the folder of messages you wish to move with the `--directory` argument. This will move all the messages in the specified folder and its sub-folders.
Container-based deployments make use of `hqfmove` to handle scaling-down requests, moving messages from the container being removed to another container.
For more information, see the [Halon Docker](https://github.com/halon/halon-docker/) repository.
# Message suspension
The pickup policy subsystem implements a suspension list to enforce concurrency, rate and connectinterval limits.
This suspension list can be used directly to temporarily pause and resume traffic in the active queue.
For a more permanent suspension, like a quarantine for spam, put messages on hold instead.
The default
[start configuration](#config_startup)’s
[`environment.suspendconf`](#config_startup:confval-environment.suspendconf) directive
loads it from */etc/halon/smtpd-suspend.yaml*.
It is described by, and can be validated with, the
[smtpd-suspend.schema.json](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/6.12-stable/smtpd-suspend.schema.json)
[JSON schema](https://docs.halon.io/json-schemas/6.12-stable/) (included in our [Visual Studio Code integration](#vscode)).
In addition to the configuration file on disk, policy conditions can be added on the fly over the
[Protocol Buffer](https://docs.halon.io/protobuf-schemas/6.12-stable/) API’s `PolicyConditionAddRequest` function, [command line interface](#cli),
[web administration](#web),
as well as from the pre- and [post-delivery script](https://docs.halon.io/hsl/archive/master/postdelivery.html).
[Lists](#config_smtpd:config-list) may be used to reference and suspend multiple field values at the same time.
The example below suspends all emails on the “customer1” transport to the destination “gmail.com”:
```yaml
suspends:
- transportid: customer1
recipientdomain: gmail.com
```
## Excluding IPs from pool
A queued email can have multiple designated source IPs, which can be used to load balance between a pool
of source IPs in a round-robin fashion. The list of source IPs (and matching HELO hostnames) can be configured per transport,
or overridden by the “sourceip” argument to the [pre-delivery script](https://docs.halon.io/hsl/archive/master/predelivery.html)’s `Try()` function.
Queuing an email with multiple source IPs has the benefit of being able to dynamically suspend specific source IPs,
while still allowing a queued email to be sent using the other IPs. Consider the following example:
```yaml
suspends:
- localip: 192.0.2.1
recipientdomain: gmail.com
```
Since `localip` is a list, emails queued with both “192.0.2.1” and another IP will still be sent to “gmail.com”
from that other source IP.
# Changing message states
Messages are automatically moved between the
active and defer queue in accordance with the normal behavior of an MTA (try-defer-retry-defer, etc).
Messages can also be forcibly moved between queues.
Messages can be updated using
the [Protocol Buffer](https://docs.halon.io/protobuf-schemas/6.12-stable/) API’s `QueueUpdate` function, [command line interface](#cli),
[web administration](#web),
as well as from the pre- and [post-delivery script](https://docs.halon.io/hsl/archive/master/postdelivery.html).
This changes both the information on disk and in memory (the queue process’s data structures).
When a message is moved, it is re-injected via the “update” actor described
in the [activity diagram](#queue:activity-diagram-label).
To put queued messages for example.com on hold:
```console
$ halonctl queue update --recipientdomain example.com --state DEFER --state ACTIVE --hold
10 messages affected
```
Messages that are in a “working” (for example, being delivered) will be unaffected.
To immediately retry a certain deferred message by ID:
```console
$ halonctl queue update --id 44815e44-05d3-11ea-b388-06f091ceb4dc:1 --state DEFER --active
1 messages affected
```
## Messages on hold
Messages, or copies of messages, can be put in a frozen *HOLD* state.
This usually done from the end-of-data [script](https://docs.halon.io/hsl/) to implement for example short-term
quarantines. In order to enforce a retention policy for those messages, you can run
[halonctl](#cli) from *cron*:
```console
# crontab -l
*/5 * * * * halonctl queue update --delete --state HOLD --age '>86400' --metadata qid=1day
*/5 * * * * halonctl queue update --delete --state HOLD --age '>604800' --metadata qid=7days
```
# Spool startup
Upon startup the spool directory ([`spool.path`](#config_startup:confval-spool.path) directive) is scanned for queued messages (.hqf files). The queue files are check for read and write permission by the running process (so that they may be updated and unlinked).
The files are then opened and parsed by multiple concurrent threads ([`spool.loader.threads`](#config_startup:confval-spool.loader.threads) directive) before either adding them to the defer queue, hold queue or active queue.
> * If there is an error during the load the file is skipped and an error is logged.
> * If the file is empty or contains no queue information (hence from an incomplete transaction), you may choose to unlink, rename, ignore the file (`spool.corrupt` directive)
# On-disk format
Queued messages and their metadata is written to disk, so that they can be re-loaded into
the MTA in case the *smtpd* [program](#install:comp-programs) is restarted. The on-disk metadata
can be inspected with the *hqfcat* Python script:
```console
$ hqfcat /var/spool/halon/queue/66/662ffb4a-02d1-11ea-bcf7-06f091ceb4dc.hqf
[
{
"status": "QUEUE",
"retry": 23,
"retryts": 1573508496,
"transportid": "mx",
"ts": 1573290908,
...
```
which produces an output similar to the regular [command line interface](#cli):
```console
$ halonctl queue list --id b186d406-0c36-11ea-8dda-005056914940:1 --json
[
{
"localips": [],
"tags": [],
"metadata": {},
"id": {
"transaction": "b186d406-0c36-11ea-8dda-005056914940",
"queue": 1
},
...
```
You can view the message including modifications made by the end-of-DATA [script](https://docs.halon.io/hsl/) using the *hqfeml* script.
The `-R` flag is the recipient queue id. Without the `-R` flag the original message is displayed.
By comparing the *hqfeml* output with the original message, you can see the modifications:
```console
$ hqfeml -R 1 /var/spool/halon/queue/b1/b186d406-0c36-11ea-8dda-005056914940.hqf
```
# Queue statistics via CLI/API
The [Protocol Buffer](https://docs.halon.io/protobuf-schemas/6.12-stable/) API and CLI provides [statistics](#cli_stats:cli-stats):
**Queue size:**
```console
$ halonctl process-stats | grep queue.queue
queue.queue.defer.size: 67
queue.queue.active.size: 6
```
**Pickup:**
```console
$ halonctl process-stats | grep queue.pickup
queue.pickup.skips: 86826
queue.pickup.misses: 9034
```
**Policy:**
```console
$ halonctl process-stats | grep queue.policy
queue.policy.concurrency.counters: 0
queue.policy.concurrency.suspends: 0
queue.policy.rate.buckets: 15
queue.policy.rate.suspends: 5
queue.policy.dynamic.suspends: 0
queue.policy.dynamic.conditions: 0
queue.policy.connectinterval.buckets: 0
queue.policy.connectinterval.suspends: 0
```
**Pooling:**
```console
$ halonctl process-stats | grep queue.connections.pooling
queue.connections.pooling.size: 2
queue.connections.pooling.hits: 3853
queue.connections.pooling.misses: 5900
queue.connections.pooling.expires: 5694
queue.connections.pooling.evicts: 0
queue.connections.pooling.skips: 0
```
# Command line interface
There are three primary ways of managing hosts (or clusters of);
the [web administration](#web), API ([Protocol Buffer](https://docs.halon.io/protobuf-schemas/6.12-stable/)) or CLI.
The `smtpd` package comes with a program called `halonctl` that
can be used to manage any aspect of the MTA. This section is a guide for
that program.
Every command have a `--help` flag to display available options.
The *halonctl* program communicates with the `smtpd`
processes over [control sockets](#api_control_sockets) as
described by the [Protocol Buffer](https://docs.halon.io/protobuf-schemas/6.12-stable/) schemas.
The raw response data can be useful
when using the CLI for scripting with tools like *jq*,
and the request data can be useful when learning about the API.
Therefore, the commands have a `--json` output flag making it return the raw response
and a `--json-request` flag that prints the raw request (JSON encoded).
* [Queue management](#cli_queue)
* [List messages](#cli_queue:list-messages)
* [Export messages](#cli_queue:export-messages)
* [Take actions on messages](#cli_queue:take-actions-on-messages)
* [Actions and working states](#cli_queue:actions-and-working-states)
* [Show messages distribution](#cli_queue:show-messages-distribution)
* [Show message structure in queue](#cli_queue:show-message-structure-in-queue)
* [Active queue policies](#cli_queue:active-queue-policies)
* [Active queue suspensions](#cli_queue:active-queue-suspensions)
* [Active queue delivery settings](#cli_queue:active-queue-delivery-settings)
* [Moving the queue files with *hqfmove*](#cli_queue:moving-the-queue-files-with-hqfmove)
* [Trace messages](#cli_queue:trace-messages)
* [Loading queue files](#cli_queue:loading-queue-files)
* [Configuration management](#cli_config)
* [Script syntax and packing](#cli_config:script-syntax-and-packing)
* [Reloading configurations](#cli_config:reloading-configurations)
* [Blue-green testing](#cli_config:blue-green-testing)
* [Remote configuration](#cli_config:remote-configuration)
* [Script engine](#cli_script)
* [Shared memory functions](#cli_script:shared-memory-functions)
* [Cache statement](#cli_script:cache-statement)
* [DNS resolver](#cli_dns)
* [Statistics and information](#cli_stats)
* [halontop](#cli_stats:halontop)
* [halonctl process-stats](#cli_stats:halonctl-process-stats)
* [Metrics summary](#openmetrics)
* [Script interpreter](#cli_hsh)
* [Options](#cli_hsh:options)
* [Script interpreter](#cli_hsh:id1)
* [Interactive shell](#cli_hsh:interactive-shell)
# Queue management
These sub-commands operate on the [queue](#queue)
(which contains queued email messages) in various ways.
Please see the [queue](#queue) section for more information
about what the queue is, and how it works.
Because the metadata for all messages in queue is loaded into memory,
the MTA can quickly summarize and modify large number of messages.
The *list*, *groupby* and *update* sub-commands share the same
condition arguments, which are used to filter the result:
```console
$ halonctl queue list -h
...
Conditions:
--transportid Filter on transport ID (repeatable)
--jobid Filter on job ID (repeatable)
--senderdomain Filter on sender domain (repeatable)
--recipientdomain Filter on recipient domain (repeatable)
--sender Filter on sender (repeatable)
--recipient Filter on recipient (repeatable)
--id Filter on ID (repeatable)
--state Filter on state (repeatable)
--retrycount Filter on retry count (operators, repeatable)
--retrydelay Filter on next retry (operators, repeatable)
--age Filter on age (operators, repeatable)
--ts Filter on received time (operators, repeatable)
--metadata Filter on metadata
Available states are:
queue: ACTIVE, DEFER
working: SENDING, UNKNOWN
freeze: HOLD, UPDATE
Number and date format:
operators: >, >=, <, <= and = (eg ">2020-01-01T01:02:03")
range: .. (eg "10..20")
...
```
## List messages
This command shows information about messages in queue,
filtered by the [conditions](#cli_queue:halonctl-queue-conditions) described above.
There are a few pre-defined views for showing the defer queue, active queue and
open connections with relevant table fields pre-selected.
```console
$ halonctl queue list --age '>3600' --state DEFER -F id -F retrycount -F retrydelay -F recipient
┌────────────────────────────────────────┬────────────┬────────────┬─────────────────┐
│ id │ retrycount │ retrydelay │ recipient │
├────────────────────────────────────────┼────────────┼────────────┼─────────────────┤
│ c6321f4c-1671-11ea-885e-005056914940:1 │ 20 │ 243 │ foo@example.com │
│ f640a91a-1671-11ea-b6ea-005056914940:1 │ 21 │ 70 │ bar@example.org │
...
```
## Export messages
This command exports a messages in queue in “message/rfc822” format. If only a messageid (and no :queueid is provided) the original message (as received) will be exported.
```console
$ halonctl queue export c6321f4c-1671-11ea-885e-005056914940
...
$ halonctl queue export c6321f4c-1671-11ea-885e-005056914940:1
...
```
## Take actions on messages
The *queue update* command can take various actions on messages,
filtered by the [conditions](#cli_queue:halonctl-queue-conditions) described above.
This command can perform the updates synchronously or asynchronously, see the `--background` and `--progress` options.
* `--delete` remove messages, without sending bounces
* `--reason` set the DSN diagnosticcode when deleting the message (“Message could not be delivered in the allotted time frame”)
* `--bounce` remove messages, and generate bounce messages to the sender
* `--dsn-status` set the DSN status when bouncing the message (5.4.7)
* `--dsn-diagnosticcode` set the DSN diagnosticcode when bouncing the message (“Message could not be delivered in the allotted time frame”)
* `--active` move messages (that are deferred or on hold) to the active queue for imminent delivery
* `--defer` move messages (that are in active queue or on hold) to the defer queue for the specified number of seconds (number)
* `--retrytsjitter` apply an optional jitter so that messages are spread evenly between the defer time and an additional jitter in seconds
* `--hold` put queued messages on hold
* `--updatetransportid` changes the messages’ transport ID to the one specified by the argument (string)
* `--updatemetadata` modify or add auxiliary metadata to the messages as specified by the argument (in format *field=value*)
* `--updatepriority` change the priority of the message (0 - normal, 1 - high, number)
```console
$ halonctl queue update --age '>3600' --state DEFER --state ACTIVE --bounce
230 messages affected
$ halonctl queue update --transportid t1 --state DEFER --updatetransportid t2
27854 messages affected
```
### Actions and working states
Messages in a *working* state
(delivery attempts, script execution, DNS resolution, etc)
cannot be directly modified, and they will be reported as *unaffected* by the command.
Sometimes it is important to perform a task on all matching messages, even those that are in a working state.
For example, you might want to delete every possible queued message from a certain sender domain.
To do this, re-run the command with the `--freeze` flag.
This will capture those messages once they are no longer in a working state
and put them in the so-called *UPDATE* state
(given that they still in in queue;
messages that have been delivered or deleted can not be captured).
No messages will be reported as *unaffected*;
they will instead be reported as *freezing*.
You should then monitor the `queue.freeze.update.pending` process counter
and wait for it to reach 0.
When it does, every messages that could not be immediately modified (but which is still in queue)
will be in the *UPDATE* state, ready for you to modify them.
```console
$ halonctl queue update --delete --senderdomain example.com --all-states
100 messages affected
30 messages unaffected
$ halonctl queue update --delete --senderdomain example.com --all-states --freeze
4 messages affected
26 messages freezing
$ halonctl process-stats | grep queue.freeze.update
queue.freeze.update.size: 6
queue.freeze.update.pending: 20
$ halonctl process-stats | grep queue.freeze.update
queue.freeze.update.size: 22
queue.freeze.update.pending: 0
$ halonctl queue update --delete --senderdomain example.com --state UPDATE
22 messages affected
```
## Show messages distribution
This command shows distribution of messages in queue,
filtered by the [conditions](#cli_queue:halonctl-queue-conditions) described above.
There are a few pre-defined views for showing the distribution by for example age.
```console
$ halonctl queue groupby --view state
┌─────────────┬─────────────────┬───────┬────────┬───────┬──────┬─────────┬─────────┐
│ transportid │ recipientdomain │ total │ ACTIVE │ DEFER │ HOLD │ UNKNOWN │ SENDING │
├─────────────┼─────────────────┼───────┼────────┼───────┼──────┼─────────┼─────────┤
│ inbound │ example.com │ 19 │ 0 │ 17 │ 1 │ 0 │ 1 │
...
```
## Show message structure in queue
This command shows how messages are distributed inside of the queue. The output of this commands
shows how many total and unique items on each field in the queue (as if it would be a tree).
It can be used to understanding memory usage an performance of the queue.
```console
$ halonctl queue analyze
┌─────────────────┬───────┬────────┐
│ Field │ Total │ Unique │
├─────────────────┼───────┼────────┤
│ prio │ 1 │ 1 │
│ tenantid │ 9 │ 9 │
│ jobid │ 21 │ 20 │
│ transportid │ 76 │ 12 │
│ localip │ 116 │ 13 │
│ grouping │ 435 │ 52 │
│ remoteip │ 1921 │ 117 │
│ remotemx │ 1921 │ 65 │
│ recipientdomain │ 5258 │ 67 │
└─────────────────┴───────┴────────┘
```
## Active queue policies
These sub-commands can add, list and delete dynamic
[active queue policies](#queue_policy:queue-policy) that overrides
the ones in the [`environment.policyconf`](#config_startup:confval-environment.policyconf) configuration,
as well as reload that configuration file.
```console
$ halonctl queue policy add -F remotemx --remotemx *.example.com --rate 10/600 --ttl 3600
30be002e-7436-11eb-ae42-5254004d77d3
$ halonctl queue policy list
┌──────────────────────────────────────┬──────────┬─────────────┬─────────┬──────────┬────────────────┬─────────────────┬───────┬──────────┬─────────────┬────────┬─────────┬─────┐
│ id │ fields │ transportid │ localip │ remoteip │ remotemx │ recipientdomain │ jobid │ grouping │ concurrency │ rate │ ttl │ tag │
├──────────────────────────────────────┼──────────┼─────────────┼─────────┼──────────┼────────────────┼─────────────────┼───────┼──────────┼─────────────┼────────┼─────────┼─────┤
│ 30be002e-7436-11eb-ae42-5254004d77d3 │ remotemx │ │ │ │ *.example.com │ │ │ │ │ 10/600 │ 3592.44 │ │
└──────────────────────────────────────┴──────────┴─────────────┴─────────┴──────────┴────────────────┴─────────────────┴───────┴──────────┴─────────────┴────────┴─────────┴─────╯
$ halonctl queue policy delete 30be002e-7436-11eb-ae42-5254004d77d3
```
It is also possible to clear or partially refill active queue policy rates,
for a specific policy rate “bucket” (matched exactly by the optional condition arguments).
If you run it without specifying a bucket, it will clear all rate buckets,
created by any policy, and return the number of buckets it cleared/refilled.
```console
$ halonctl queue policy rate refill --localip 198.51.100.4 --remotemx mx1.example.org
1
```
## Active queue suspensions
These sub-commands can add, list and delete dynamic
[active queue suspension](#queue_suspend:queue-suspend) that overrides
the ones in the [`environment.suspendconf`](#config_startup:confval-environment.suspendconf) configuration,
as well as reload that configuration file.
```console
$ halonctl queue suspend add --remotemx *.example.com --ttl 60
803c41d5-4a8e-424a-97e9-f8db55f69179
$ halonctl queue suspend list
┌──────────────────────────────────────┬─────────┬─────────────┬─────────┬──────────┬───────────────┬─────────────────┬───────┬──────────┬─────────┬─────┐
│ id │ source │ transportid │ localip │ remoteip │ remotemx │ recipientdomain │ jobid │ grouping │ ttl │ tag │
├──────────────────────────────────────┼─────────┼─────────────┼─────────┼──────────┼───────────────┼─────────────────┼───────┼──────────┼─────────┼─────┤
│ 803c41d5-4a8e-424a-97e9-f8db55f69179 │ dynamic │ │ │ │ *.example.com │ │ │ │ 57.7753 │ │
└──────────────────────────────────────┴─────────┴─────────────┴─────────┴──────────┴───────────────┴─────────────────┴───────┴──────────┴─────────┴─────╯
$ halonctl queue suspend delete 803c41d5-4a8e-424a-97e9-f8db55f69179
```
## Active queue delivery settings
This sub-command can reload the
[active queue delivery settings](#queue_delivery:queue-delivery) configuration file
[`environment.deliveryconf`](#config_startup:confval-environment.deliveryconf).
```console
$ halonctl queue delivery reload
```
## Moving the queue files with *hqfmove*
If you need to move the queued message [files](#queue_load:queue-ondisk) (for example from one MTA to another)
it is important to first “unload” them. If you want to move the entire queue, the easiest way is to simply
shut down the [server process](#install:comp-programs) and use the `hqfmove` tool. To move specific messages, it is recommended to use the
*queue unload* command, filtered by the [conditions](#cli_queue:halonctl-queue-conditions) described above.
* `--partial` match a queued message, even if only one of its recipients matches
* `--freeze` [freeze messages](#cli_queue:halonctl-queue-freeze) that are in a working state
```console
$ halonctl queue unload --transportid t1
/storage/mail/spool/13/13ef04db-1e06-11ea-927e-0050569a4c9c.hqf unloaded
/storage/mail/spool/1b/1b5a5e80-1da2-11ea-927e-0050569a4c9c.hqf unloaded
...
$ halonctl queue unload
No messages unloaded
```
`hqfmove` command-line arguments:
```default
--server address:port/unix-socket [--server address:port/unix-socket] --directory /path [--non-interactive] [--verbose] [--progress] [--tls-certificate /cert.pem] [--tls-privatekey /key.pem] [--tls-verifyname example.com] [--no-tls-verify] [--rate x/y]
```
One **or more** destination MTAs can be specified with `--server` arguments.
Each specifies the hostname/IP address and the destination’s [`environment.controlsocket.port`](#config_startup:confval-environment.controlsocket.port), or else the [`environment.controlsocket.path`](#config_startup:confval-environment.controlsocket.path).
- If many `--server` destination hosts are given, messages will be pushed to destinations in round-robin fashion.
- If a hostname A record resolves to multiple hosts, messages will also be pushed to destinations in round-robin fashion.
For working with clusters, see [Moving messages between instances](#queue_cluster:cluster-moving).
## Trace messages
This command allows you to get debug or trace information regarding message deliveries in queue,
filtered by the queue pickup fields available (eg. `--remotemx '*.example.com'`) or a specific transaction id.
Additional options are available to control the pooling (`--connect`) and PIPELINING (`--no-pipelining`) behavior.
```console
$ halonctl queue trace
2023-03-28 21:59:23.076 Begin trace of message 18096a8d-cba3-11ed-b464-930f4c7d94ae:1
2023-03-28 21:59:23.076 Connecting from [1.2.3.4] to [5.6.7.8]:25 (mx.example.com) (DNSSEC)
2023-03-28 21:59:23.079 Connected
2023-03-28 21:59:23.083 [SMTP] < 220 mx.example.com ESMTP
2023-03-28 21:59:23.083 [SMTP] > EHLO mx.example.org
...
```
## Loading queue files
If you need to manually import a queue file into a running MTA instance you can use the *queue load* command. The “load” command will copy the .hqf specified to the
spool folder leaving the original file in place. The message ID (in the HQF file’s ID field) has to be an unique and will be used as the target file name (regardless of the imported source file name). If the HQF file’s ID field is an all zero UUID (00000000-0000…) then a new UUID will be generated.
* `--hqf` path to the queue file
```console
$ halonctl queue load --hqf 13ef04db-1e06-11ea-927e-0050569a4c9c.hqf
Message loaded
```
# Configuration management
These sub-commands can reload the [running configuration](#config_smtpd)
(including its Halon [script](https://docs.halon.io/hsl/))
and control the built-in blue-green testing that we call “live stage”.
See the [configuration](#config) section for more information.
## Script syntax and packing
The [script](https://docs.halon.io/hsl/) is normally edited as individual files
using the [Visual Studio Code integration](#vscode) or simply a text editor.
Those are then checked and “packed” into an actual configuration
file by the *halonconfig* script as described in the
[script directive](#config_smtpd:config-script) section.
Given that the folder *test* contains *halonconfig*’s
standard file structure (see */opt/halon/share/examples*) you can
pack, validate and reload the configuration directly on the MTA by:
```console
$ cd test
(editing configuration files)
$ halonconfig
# cp dist/* /etc/halon/
# service halon reload
```
If you want to exclude any files inside the *src/files* folder from being packed
you can add a *.halonignore* file which supports regular glob patterns.
The *halonconfig* command may also be used to “unpack” the configuration,
reversing the described process above.
## Reloading configurations
You can also invoke the individual reload commands directly:
```console
$ halonctl config reload # /etc/halon/smtpd-app.yaml
$ halonctl queue policy reload # /etc/halon/smtpd-policy.yaml
$ halonctl queue suspend reload # /etc/halon/smtpd-suspend.yaml
$ halonctl queue delivery reload # /etc/halon/smtpd-delivery.yaml
```
## Blue-green testing
Halon has built-in blue-green testing, which allows you to deploy a
parallel [running configuration](#config_smtpd) for only a selection of the
traffic, for example connections from certain IP addresses or a certain percentage
chosen by random. The following example will deploy a parallel configuration
for 10% of connecting clients during one hour:
```console
$ halonctl config livestage start --id t1 --file dist/smtpd-app.yaml --probability 0.1 --time 3600
$ halonctl config livestage status
t1: 2 connections(s), 30/3600 runtime
$ halonctl config livestage cancel
```
## Remote configuration
The `halonconfig` script can be used to pack and validate configuration
folders on any computer that runs Python. You can copy it from the MTA:
```console
$ python -m venv env
$ source env/bin/activate
$ pip install pyyaml
$ pip install jsonschema
$ scp mta:/opt/halon/bin/halonconfig .
$ scp mta:/opt/halon/share/json-schemas json
```
and run it by specifying the JSON schema path and disabling the script linter:
```console
$ python halonconfig --schema-dir json --no-check-hsl
```
You can start a live stage directly from a remote computer by using SSH stdin redirection:
```console
$ cat dist/smtpd-app.yaml | ssh mta "halonctl config livestage start --file /dev/stdin --id t2"
$ ssh mta "halonctl config livestage status"
t2: 2 connections(s), 3 runtime
```
# Script engine
These sub-commands can work on various parts of the Halon [script](https://docs.halon.io/hsl/) engine.
The actual script is managed via the [configuration](#cli_config:cli-config) commands.
## Shared memory functions
These sub-commands can store, list, fetch and delete key/value pairs from the
the [script](https://docs.halon.io/hsl/) engine’s
[shared memory](https://docs.halon.io/hsl/functions.html#shared-memory) API.
In order to be able to work with complex Halon [script](https://docs.halon.io/hsl/) data structures,
the values are JSON encoded.
```console
$ halonctl hsl memory store theanswer 42
$ halonctl hsl memory list
theanswer
$ halonctl hsl memory fetch theanswer
42
$ halonctl hsl memory delete theanswer
```
## Cache statement
These sub-commands can list and clear the [script](https://docs.halon.io/hsl/) engine’s
[function cache](https://docs.halon.io/hsl/structures.html#cache)
buckets.
```console
$ halonctl hsl cache list
┌───────────┬──────────────────┬──────────┬──────┬────────┬────────┬────────┬──────────┐
│ Namespace │ Function │ Max size │ Size │ Hits │ Misses │ Evicts │ Hit rate │
├───────────┼──────────────────┼──────────┼──────┼────────┼────────┼────────┼──────────┤
│ │ api_call_http │ 100000 │ 845 │ 122799 │ 6041 │ 0 │ 95.3112 │
│ │ http │ 16384 │ 747 │ 5670 │ 2850 │ 0 │ 66.5493 │
│ test │ smtp_lookup_rcpt │ 16384 │ 2 │ 1 │ 31 │ 0 │ 3.125 │
└───────────┴──────────────────┴──────────┴──────┴────────┴────────┴────────┴──────────╯
$ halonctl hsl cache clear --function http
1 buckets cleared
```
# DNS resolver
These commands can clear and view statistics about the
asynchronous DNS resolver’s built-in cache.
```console
$ halonctl process-stats | grep resolver.cache
resolver.cache.size: 5
resolver.cache.hits: 897
resolver.cache.misses: 1624
resolver.cache.expires: 1566
resolver.cache.evicts: 0
resolver.cache.skips: 0
$ halonctl resolver clear
```
There is also a command to test the DNS resolvers behavior as for choosing the next-hop based on a transactionid and retry count.
```console
$ halonctl resolver query --seed 67b06545-2592-11eb-a941-f1a44e94c1b9 --retry 3 halon.io
address: 2a02:750:7:3305::ee
hostname: mx.halon.io
dnssec: true
tlsa:
dnssec: true
rr:
- usage: 3
selector: 1
mtype: 1
data: 42568063726264388f17d494cd3d01079ba1df4e60b1448e5670544cd7739218
```
There is also a command to view the DNS resolvers active (running) queries.
```console
$ halonctl resolver list
┌────────────────────────────┬──────────┐
│ Hostname │ Duration │
├────────────────────────────┼──────────┤
│ example.com │ 0.664586 │
│ example.org │ 0.569450 │
│ example.net │ 0.317264 │
└────────────────────────────┴──────────┘
```
There is also a command to view the DNS resolvers queued queries.
```console
$ halonctl resolver queue
┌──────────┬───────────────────┬──────────┬──────────┐
│ Position │ Hostname │ Priority │ Dedup(s) │
├──────────┼───────────────────┼──────────┼──────────┤
│ 0 │ example.com │ 1 │ 10 │
│ 1 │ example.org │ 0 │ 4 │
│ 2 │ example.net │ 0 │ 2 │
├──────────┴───────────────────┴──────────┴──────────┤
│ showing 3 matches │
└────────────────────────────────────────────────────┘
```
# Statistics and information
Shows information about process statistics (cache hits, internal queue sizes)
and other information such as email queue size.
## halontop
The *halontop* program shows a visual view of all counters with live updates and rates calculations.

The first line shows the process ID, `smtpd` process uptime and current host time. Values in blue show the difference between the previous value and the current value, e.g. +6, -6.
Navigate between panes showing the following sections, using `<` `>` cursor keys.
SERVERS
: Name, number of inbound connections, percentage occupied.
SCRIPTS
: For each type of script hook, how many are running, finished, and pending to be executed.
Also shows script errors, if any.
The pre-delivery and post-delivery hooks are queue actions when delivering outbound messages.
The others relate to phases of the inbound SMTP conversations.
Non-zero numbers in the “pending” state show you have messages that cannot run the script hook yet. High counts can indicate the need for [Performance tuning](#config_tuning).
THREADS
: The script thread activity broken down by thread pool name. “_default” and “_queues” are the standard names.
LOADER
: Count, pending and active counts. Messages coming into the queue, on a well running system will correlate with the injection rate.
High “pending” values indicate a possible disk performance issue.
QUOTA
: Size.
QUEUES
: Active (priority 0 and 1), defer, hold, update and pending message counts.
POLICIES / DYNAMIC
: Count of suspends and conditions.
PICKUP
: Count of total messages picked up, skips, misses, pending and evals.
POLICIES / CONCURRENCY
: Outbound connection concurrency: suspends and counters.
POLICIES / RATE
: Outbound rate limiting: suspends and buckets.
CONNECTIONS
: Outbound connection concurrency, as a count and percentage of total.
POOLING
: Outbound connection pooling size, hits, misses, expires, evicts and skips.
DELIVERY
: Delivered, delayed and failed (since last restart).
RELEASE
: Pending count.
RESOLVER
: Running, pending and dedup counts. High “pending” counts may indicate a problem with DNS reolution.
RESOLVER CACHE
: Size, hits, misses, expires, evicts and skips.
DOMAIN CACHE
: Size, hits, misses, expires, and evicts.
## halonctl process-stats
Text based output for counters may be extracted using *halonctl* suited for data collection.
```console
$ halonctl process-stats
process.pid: 48535
process.runtime: 3638
...
```
Output can be native text (default), JSON or OpenMetrics format:
```console
$ halonctl process-stats --help
Show smtpd process information
Usage: /opt/halon/bin/halonctl.bin process-stats [OPTIONS]
Options:
-h,--help Print this help message and exit
--json Excludes: --openmetrics
Dump response as JSON
--json-request Dump request as JSON
--openmetrics Excludes: --json
Dump response as OpenMetrics
```
[OpenMetrics](https://openmetrics.io/) format can be used directly with monitoring tools such as [Prometheus](https://prometheus.io/) and [Kibana](https://www.elastic.co/kibana).
The output is self-describing, with `# TYPE`, `# HELP` and `# UNIT` metadata directives; see [Metrics summary](#openmetrics).
# Metrics summary
The following standard metrics are output by the [halonctl process-stats](#cli_stats:halonctl-process-stats) command and the [monitor](#api_monitor) interface in OpenMetrics format.
These can be extended by defining your own [`monitor.metrics`](#config_smtpd:confval-monitor.metrics) in the running configuration, and updating them via HSL script functions [metrics_increment](https://docs.halon.io/hsl/functions.html#metrics_increment/), [metrics_decrement](https://docs.halon.io/hsl/functions.html#metrics_decrement/), and [metrics_set](https://docs.halon.io/hsl/functions.html#metrics_set/).
| Name | Description | Type | Unit | Labels | Reference |
|---------------------------------------------|----------------------------------------------------------------------------|---------|---------|-------------------|---------------------------------------------------------------------------------------------------------------------|
| halon_cluster_connected | The current connection status (true=1, false=0) | gauge | | | |
| halon_cluster_hosts | Number of peers in the cluster | gauge | | | |
| halon_config_active | Active configurations | gauge | | | |
| halon_config_reloads | Configuration reloads | gauge | | | |
| halon_monitor_ready | The current ready status (ready=1, notready=0) | gauge | | | [system health](#api_monitor) |
| halon_monitor_healthy | The current health status (healthy=1, unhealthy=0) | gauge | | | [system health](#api_monitor) |
| halon_process_license_expiration | License expiration | counter | seconds | | |
| halon_process_pid | Process PID | gauge | | | |
| halon_process_runtime | Process runtime | counter | seconds | | |
| halon_process_version_major | Major version | counter | | | |
| halon_process_version_minor | Minor version | counter | | | |
| halon_process_version_patch | Patch version | counter | | | |
| halon_queue_connections_concurrent | Number of open connections | gauge | | | [`queues.concurrency.total`](#config_smtpd:confval-queues.concurrency.total) |
| halon_queue_connections_maxconcurrent | Configured maximum number of open connections | counter | | | [`queues.concurrency.total`](#config_smtpd:confval-queues.concurrency.total) |
| halon_queue_connections_pooling_evicts | Number of connections expired from the connection cache, due to size | counter | | | |
| halon_queue_connections_pooling_expires | Number of connections expired from the connection cache, due to TTL | counter | | | [Delivery settings](#queue_delivery:queue-delivery) |
| halon_queue_connections_pooling_hits | Number of connections reused in the connection cache | counter | | | |
| halon_queue_connections_pooling_maxsize | Configured maximum number of connections in the connection cache | counter | | | [`queues.pooling.size`](#config_smtpd:confval-queues.pooling.size) |
| halon_queue_connections_pooling_misses | Number of connections not found in the connection cache | counter | | | |
| halon_queue_connections_pooling_size | Number of connections in the connection cache | gauge | | | [`queues.pooling.size`](#config_smtpd:confval-queues.pooling.size) |
| halon_queue_connections_pooling_skips | Number of connections not added to the connection cache, due to being full | counter | | | [Delivery settings](#queue_delivery:queue-delivery) |
| halon_queue_delivery_delayed | Number of soft-failed delivery events | counter | | | |
| halon_queue_delivery_delivered | Number of messages successfully delivered | counter | | | |
| halon_queue_delivery_failed | Number of hard-failed delivery events | counter | | | |
| halon_queue_freeze_hold_size | Number of messages in the hold queue | gauge | | | |
| halon_queue_freeze_update_pending | Number of pending messages to be added to the update queue | gauge | | | [Actions and working states](#cli_queue:halonctl-queue-freeze) |
| halon_queue_freeze_update_size | Number of messages in the update queue | gauge | | | [Actions and working states](#cli_queue:halonctl-queue-freeze) |
| halon_queue_loader_active | Number of messages loaded in memory | gauge | | | |
| halon_queue_loader_count | Cumulative number of messages loaded into memory | counter | | | [Queuing subsystem](#queue) |
| halon_queue_loader_maxactive | Maximum number of messages loaded in memory | counter | | | [`queues.maxmessages`](#config_smtpd:confval-queues.maxmessages) |
| halon_queue_loader_pending | Number of pending messages to be loaded into memory | gauge | | | |
| halon_queue_pickup_count | Number of messages picked up from the active queue | counter | | | |
| halon_queue_pickup_evals | The suspend evaluations against the list of suspends | counter | | | |
| halon_queue_pickup_misses | Number of times the queue was searched without any message to deliver | counter | | | |
| halon_queue_pickup_pending | Number of messages picked up from the queue pending for delivery | gauge | | | |
| halon_queue_pickup_putback | Number of times a picked up message was put back into the queue | counter | | | |
| halon_queue_pickup_requeue | Number of times a picked up message was requeued by a plugin | counter | | | |
| halon_queue_pickup_skips | Number of queue nodes skipped during message pickup | counter | | | |
| halon_queue_policy_concurrency_counters | Number of concurrency counters | gauge | | | [Queue policies](#queue_policy:queue-policy) |
| halon_queue_policy_concurrency_suspends | Number of suspended concurrency counters | gauge | | | [Message suspension](#queue_suspend:queue-suspend) |
| halon_queue_policy_connectinterval_buckets | Number of connect interval buckets active for the current queue | gauge | | | |
| halon_queue_policy_connectinterval_suspends | Number of suspended connect interval buckets | gauge | | | |
| halon_queue_policy_dynamic_conditions | Number of dynamically added policies | gauge | | | [script](https://docs.halon.io/hsl/) |
| halon_queue_policy_dynamic_suspends | Number of dynamically added suspensions | gauge | | | [script](https://docs.halon.io/hsl/) |
| halon_queue_policy_rate_buckets | Number of rate buckets active for the current queue | gauge | | | [Queue policies](#queue_policy:queue-policy) |
| halon_queue_policy_rate_suspends | Number of suspended rate buckets | gauge | | | [Message suspension](#queue_suspend:queue-suspend) |
| halon_queue_queue_active_priorities_size | Number of messages in the active queue based on priorities | gauge | | priority | [Queuing subsystem](#queue) |
| halon_queue_queue_active_size | Number of messages in the active queue | gauge | | | [Queuing subsystem](#queue) |
| halon_queue_queue_defer_size | Number of messages in the deferred queue | gauge | | | [Queuing subsystem](#queue) |
| halon_queue_quota_size | Number of quota counters | gauge | | | |
| halon_queue_release_pending | Number of messages pending to be queued to run the post-delivery script | gauge | | | |
| halon_queue_scripts_postdelivery_errors | Number of erroneous script executions (postdelivery) | counter | | threadid | |
| halon_queue_scripts_postdelivery_finished | Number of finished script executions (postdelivery) | counter | | threadid | |
| halon_queue_scripts_postdelivery_pending | Number of pending script executions (postdelivery) | gauge | | threadid | |
| halon_queue_scripts_postdelivery_running | Number of running script executions (postdelivery) | gauge | | threadid | |
| halon_queue_scripts_postdelivery_suspended | Number of suspended script executions (postdelivery) | gauge | | threadid | |
| halon_queue_scripts_postdelivery_threads | Number of threads utilized for script executions (postdelivery) | gauge | | threadid | |
| halon_queue_scripts_predelivery_errors | Number of erroneous script executions (predelivery) | counter | | threadid | |
| halon_queue_scripts_predelivery_finished | Number of finished script executions (predelivery) | counter | | threadid | |
| halon_queue_scripts_predelivery_pending | Number of pending script executions (predelivery) | gauge | | threadid | [`scripting.hooks.predelivery`](#config_smtpd:confval-scripting.hooks.predelivery) |
| halon_queue_scripts_predelivery_running | Number of running script executions (predelivery) | gauge | | threadid | [`queues.threads.script`](#config_startup:confval-queues.threads.script) |
| halon_queue_scripts_predelivery_suspended | Number of suspended script executions (predelivery) | gauge | | threadid | |
| halon_queue_scripts_predelivery_threads | Number of threads utilized for script executions (predelivery) | gauge | | threadid | |
| halon_resolver_cache_evicts | Number of entries expired from the DNS query cache, due to cache size | counter | | | |
| halon_resolver_cache_expires | Number of entries expired from the DNS query cache, due to TTL | counter | | | [`resolver.cache.ttl.min`](#config_smtpd:confval-resolver.cache.ttl.min) |
| halon_resolver_cache_hits | Number of DNS query cache hits | counter | | | |
| halon_resolver_cache_maxsize | Configured maximum number of entries in the DNS query cache | counter | | | [`resolver.cache.size`](#config_smtpd:confval-resolver.cache.size) |
| halon_resolver_cache_misses | Number of DNS query cache misses | counter | | | |
| halon_resolver_cache_size | Number of entries in the DNS query cache | gauge | | | [`resolver.cache.size`](#config_smtpd:confval-resolver.cache.size) |
| halon_resolver_cache_skips | Number of entries not added to the DNS query cached (eg. bad responses) | counter | | | |
| halon_resolver_dedup | Number of duplicate pending DNS queries | gauge | | | |
| halon_resolver_domain_cache_evicts | Number of entries evicted from the domain cache, due to cache size | counter | | | |
| halon_resolver_domain_cache_expires | Number of entries expired from the domain cache, due to TTL | counter | | | |
| halon_resolver_domain_cache_hits | Number of domain cache hits | counter | | | |
| halon_resolver_domain_cache_maxsize | Configured maximum number of entries in the domain cache | counter | | | |
| halon_resolver_domain_cache_misses | Number of domain cache misses | counter | | | |
| halon_resolver_domain_cache_size | Number of entries in the domain cache | gauge | | | |
| halon_resolver_maxrunning | Configured maximum number of running DNS queries | counter | | | [`resolver.concurrency`](#config_smtpd:confval-resolver.concurrency) |
| halon_resolver_pending | Number of pending DNS queries | gauge | | | |
| halon_resolver_running | Number of running DNS queries | gauge | | | [`resolver.concurrency`](#config_smtpd:confval-resolver.concurrency) |
| halon_servers_connections_concurrent | Number of open connections | gauge | | serverid | [`servers[].concurrency.total`](#config_smtpd:confval-servers-.concurrency.total) |
| halon_servers_connections_maxconcurrent | Configured maximum number of open connections | counter | | serverid | [`servers[].concurrency.total`](#config_smtpd:confval-servers-.concurrency.total) |
| halon_servers_scripts_auth_errors | Number of erroneous script executions (AUTH) | counter | | serverid,threadid | |
| halon_servers_scripts_auth_finished | Number of finished script executions (AUTH) | counter | | serverid,threadid | |
| halon_servers_scripts_auth_pending | Number of pending script executions (AUTH) | gauge | | serverid,threadid | |
| halon_servers_scripts_auth_running | Number of running script executions (AUTH) | gauge | | serverid,threadid | |
| halon_servers_scripts_auth_suspended | Number of suspended script executions (AUTH) | gauge | | serverid,threadid | |
| halon_servers_scripts_auth_threads | Number of threads utilized for script executions (AUTH) | gauge | | serverid,threadid | |
| halon_servers_scripts_connect_errors | Number of erroneous script executions (connect) | counter | | serverid,threadid | |
| halon_servers_scripts_connect_finished | Number of finished script executions (connect) | counter | | serverid,threadid | |
| halon_servers_scripts_connect_pending | Number of pending script executions (connect) | gauge | | serverid,threadid | [`servers[].phases.connect.hook`](#config_smtpd:confval-servers-.phases.connect.hook) |
| halon_servers_scripts_connect_running | Number of running script executions (connect) | gauge | | serverid,threadid | [`servers[].threads.script`](#config_startup:confval-servers-.threads.script) |
| halon_servers_scripts_connect_suspended | Number of suspended script executions (connect) | gauge | | serverid,threadid | |
| halon_servers_scripts_connect_threads | Number of threads utilized for script executions (connect) | gauge | | serverid,threadid | |
| halon_servers_scripts_disconnect_errors | Number of erroneous script executions (disconnect) | counter | | serverid,threadid | |
| halon_servers_scripts_disconnect_finished | Number of finished script executions (disconnect) | counter | | serverid,threadid | |
| halon_servers_scripts_disconnect_pending | Number of pending script executions (disconnect) | gauge | | serverid,threadid | |
| halon_servers_scripts_disconnect_running | Number of running script executions (disconnect) | gauge | | serverid,threadid | |
| halon_servers_scripts_disconnect_suspended | Number of suspended script executions (disconnect) | gauge | | serverid,threadid | |
| halon_servers_scripts_disconnect_threads | Number of threads utilized for script executions (disconnect) | gauge | | serverid,threadid | |
| halon_servers_scripts_eod_errors | Number of erroneous script executions (EOD) | counter | | serverid,threadid | |
| halon_servers_scripts_eod_finished | Number of finished script executions (EOD) | counter | | serverid,threadid | |
| halon_servers_scripts_eod_pending | Number of pending script executions (EOD) | gauge | | serverid,threadid | |
| halon_servers_scripts_eod_running | Number of running script executions (EOD) | gauge | | serverid,threadid | |
| halon_servers_scripts_eod_suspended | Number of suspended script executions (EOD) | gauge | | serverid,threadid | |
| halon_servers_scripts_eod_threads | Number of threads utilized for script executions (EOD) | gauge | | serverid,threadid | |
| halon_servers_scripts_helo_errors | Number of erroneous script executions (HELO) | counter | | serverid,threadid | |
| halon_servers_scripts_helo_finished | Number of finished script executions (HELO) | counter | | serverid,threadid | |
| halon_servers_scripts_helo_pending | Number of pending script executions (HELO) | gauge | | serverid,threadid | |
| halon_servers_scripts_helo_running | Number of running script executions (HELO) | gauge | | serverid,threadid | |
| halon_servers_scripts_helo_suspended | Number of suspended script executions (HELO) | gauge | | serverid,threadid | |
| halon_servers_scripts_helo_threads | Number of threads utilized for script executions (HELO) | gauge | | serverid,threadid | |
| halon_servers_scripts_mailfrom_errors | Number of erroneous script executions (MAILFROM) | counter | | serverid,threadid | |
| halon_servers_scripts_mailfrom_finished | Number of finished script executions (MAILFROM) | counter | | serverid,threadid | |
| halon_servers_scripts_mailfrom_pending | Number of pending script executions (MAILFROM) | gauge | | serverid,threadid | |
| halon_servers_scripts_mailfrom_running | Number of running script executions (MAILFROM) | gauge | | serverid,threadid | |
| halon_servers_scripts_mailfrom_suspended | Number of suspended script executions (MAILFROM) | gauge | | serverid,threadid | |
| halon_servers_scripts_mailfrom_threads | Number of threads utilized for script executions (MAILFROM) | gauge | | serverid,threadid | |
| halon_servers_scripts_proxy_errors | Number of erroneous script executions (proxy) | counter | | serverid,threadid | |
| halon_servers_scripts_proxy_finished | Number of finished script executions (proxy) | counter | | serverid,threadid | |
| halon_servers_scripts_proxy_pending | Number of pending script executions (proxy) | gauge | | serverid,threadid | |
| halon_servers_scripts_proxy_running | Number of running script executions (proxy) | gauge | | serverid,threadid | |
| halon_servers_scripts_proxy_suspended | Number of suspended script executions (proxy) | gauge | | serverid,threadid | |
| halon_servers_scripts_proxy_threads | Number of threads utilized for script executions (proxy) | gauge | | serverid,threadid | |
| halon_servers_scripts_rcptto_errors | Number of erroneous script executions (RCPTTO) | counter | | serverid,threadid | |
| halon_servers_scripts_rcptto_finished | Number of finished script executions (RCPTTO) | counter | | serverid,threadid | |
| halon_servers_scripts_rcptto_pending | Number of pending script executions (RCPTTO) | gauge | | serverid,threadid | |
| halon_servers_scripts_rcptto_running | Number of running script executions (RCPTTO) | gauge | | serverid,threadid | |
| halon_servers_scripts_rcptto_suspended | Number of suspended script executions (RCPTTO) | gauge | | serverid,threadid | |
| halon_servers_scripts_rcptto_threads | Number of threads utilized for script executions (RCPTTO) | gauge | | serverid,threadid | |
| halon_threads_scripts_maxrunning | Configured maximum number of threads that can run script executions | counter | | id | |
| halon_threads_scripts_maxscripts | Configured maximum number of script executions started (M:N) | counter | | id | |
| halon_threads_scripts_pending | Number of pending script executions | gauge | | id | |
| halon_threads_scripts_rescheduled | Number of rescheduled script executions | gauge | | id | |
| halon_threads_scripts_running | Number of threads running scripts executions | gauge | | id | |
| halon_threads_scripts_scripts | Number of script executions to be started (M:N) | gauge | | id | |
# Script interpreter
The *hsh* program is a Halon [script](https://docs.halon.io/hsl/) interpreter and interactive shell (REPL).
It allows you to easily write, run and test Halon [script](https://docs.halon.io/hsl/)
without having to deploy it into the `smtpd` server.
It implements the full [standard library](https://docs.halon.io/hsl/functions.html), but not any *smtpd* server or queue hooks.
It also doesn’t provide an interface for *halonctl*’s [script](#cli_script:halonctl-script)
commands.
## Options
### -c, --config path
In order to make the *hsh* script environment resemble the environment of *smtpd*,
it supports loading *smtpd*’s [startup configuration](#config_startup) file
(default */etc/halon/smtpd.yaml*) which in turn load the [running configuration](#config_smtpd).
This allows all paths and permission to be set up correctly, as well as virtual files [`scripting.files[]`](#config_smtpd:confval-scripting.files)
and certificates [`pki.private[]`](#config_startup:confval-pki.private).
Since that file usually drop privileges and change user, it might require *hsh -P* to be run as root.
If you don’t need the full environment, you may run without a startup configuration: */dev/null*.
### -P, --privdrop
Drop privileges (setuid / setgid) to the user specified in the startup configuration.
This may allow you to have access to various sockets, but requires root permission.
The recommended setup would be to have shared group permission with the smtpd server on various sockets and paths.
### -F, --ffi
Enable [FFI](https://docs.halon.io/hsl/functions.html#ffi) support,
which is equivalent to [`scripting.ffi`](#config_startup:confval-scripting.ffi),
without having to use [`hsh -c`](#cli_hsh:cmdoption-hsh-c) to load a startup a configuration.
### -p, --plugin id
Load a specific HSL plugin. This option is repeatable.
### -R, --rootpath path
Set the script root path,
which is equivalent to [`scripting.rootpath`](#config_startup:confval-scripting.rootpath),
without having to use [`hsh -c`](#cli_hsh:cmdoption-hsh-c) to load a startup a configuration.
### -A, --appconf path
Load a specific [running configuration](#config_smtpd)
which is equivalent to [`environment.appconf`](#config_startup:confval-environment.appconf),
without having to use [`hsh -c`](#cli_hsh:cmdoption-hsh-c) to load a startup a configuration.
### -B, --binary
Print output as binary (not as UTF-8), and also the
output of `echo` will not automatically include a newline (`\n`).
### -s, --syntax
Check the script syntax only.
### -v, --version
Display Halon version and exit.
## Script interpreter
The *hsh* program may be executed with a script file as the entry point for the script execution.
It should be given as the last argument to the hsh command line.
```console
$ cat test.hsl
echo "Hello World";
$ hsh test.hsl
Hello World
```
Setting the root to the current directory may be useful when implementing unit testing of modules:
```console
$ cat module.hsl
function foo() {
echo "Hello World";
}
$ cat test.hsl
import { foo } from "file://module.hsl";
foo();
$ hsh -R . test.hsl
Hello World
```
## Interactive shell
The *hsh* program may be executed without a file, in which case it enters the interactive shell mode (REPL).
Statements are executed line-by-line.
```console
$ hsh
HSH> echo "Hello World";
Hello World
HSH>
```
# API
The `smtpd` process is controlled via an API, which in turn is used by
management tools such as the [command line interface](#cli), [Visual Studio Code integration](#vscode),
and [web administration](#web).
Those tools can be installed on either a central location (controlling an entire cluster)
or on the MTA hosts themselves.
* [Endpoint](#api_endpoints)
* [Monitor](#api_monitor)
* [Endpoints](#api_monitor:endpoints)
* [/metrics](#api_monitor:metrics)
* [/ready](#api_monitor:ready)
* [/healthy](#api_monitor:healthy)
* [Control socket](#api_control_sockets)
* [HTTP/JSON](#api_http_json)
* [Installation](#api_install)
* [Distributions](#api_install:distributions)
* [Ubuntu](#api_install:ubuntu)
* [RHEL](#api_install:rhel)
* [Azure Linux](#api_install:azure-linux)
* [Startup](#api_install:startup)
* [Upgrade](#api_upgrade)
* [Configuration](#api_config)
* [Authentication directives](#api_config:authentication-directives)
* [Listener directives](#api_config:listener-directives)
* [Other directives](#api_config:other-directives)
* [Environment directives](#api_config:environment-directives)
* [Examples](#api_examples)
* [API reference](#api_reference)
* [Command list](#api_reference:command-list)
* [Version compatibility](#api_reference:version-compatibility)
* [HTTP submission](#http_submission)
* [Endpoint reference](#http_submission:endpoint-reference)
* [Authentication](#http_submission:authentication)
* [HTTP request format](#http_submission:http-request-format)
* [HTTP response format](#http_submission:http-response-format)
* [Examples](#http_submission:examples)
# Endpoint
The primary API is the [control socket](#api_control_sockets).
There is also a monitoring interface available which provides for [OpenMetrics](#openmetrics) and health check endpoints.
There is also a [HTTP/JSON](#api_http_json) endpoint that can be installed which sometimes cam be more convenient.
The API calls, and their respective request and response bodies
are documented in the [API reference](#api_reference).
* [Monitor](#api_monitor)
* [Endpoints](#api_monitor:endpoints)
* [/metrics](#api_monitor:metrics)
* [/ready](#api_monitor:ready)
* [/healthy](#api_monitor:healthy)
* [Control socket](#api_control_sockets)
* [HTTP/JSON](#api_http_json)
* [Installation](#api_install)
* [Distributions](#api_install:distributions)
* [Ubuntu](#api_install:ubuntu)
* [RHEL](#api_install:rhel)
* [Azure Linux](#api_install:azure-linux)
* [Startup](#api_install:startup)
* [Upgrade](#api_upgrade)
* [Configuration](#api_config)
* [Authentication directives](#api_config:authentication-directives)
* [Listener directives](#api_config:listener-directives)
* [Other directives](#api_config:other-directives)
* [Environment directives](#api_config:environment-directives)
* [Examples](#api_examples)
# Monitor
The [`monitor.type`](#config_startup:confval-monitor.type) endpoint provides an [OpenMetrics](#openmetrics) compatible endpoint as well as health check endpoints which can
signal the (un)ready and health states of the process.
This can be use useful when running behind a load balancer (such as HAProxy) or in a Kubernetes environment.
*smtpd-app.yaml*
```yaml
monitor:
apikeys:
- badsecret
```
*smtpd.yaml*
```yaml
monitor:
listener:
port: 8080
```
## Endpoints
The following (HTTP/GET) endpoints are available.
### /metrics
This endpoint provies data in a [OpenMetrics](#openmetrics) compatible format.
There are multiple types of metrics you can include in the output.
This is done by providing one or more `include` query string paramters.
| Include name | Default | Description |
|-----------------|-----------|-----------------------------------|
| `process-stats` | Yes | Provide all process-stats metrics |
| `metrics` | Yes | Provide all custom metrics |
| `hsl-cache` | No | Provide all HSL cache metrics |
*Sample request*
```console
% curl "http://mta1:8080/metrics?include=process-stats&include=hsl-cache" -H "X-API-Key: badsecret"
# TYPE halon_monitor_ready gauge
# HELP halon_monitor_ready The current ready status (ready=1, notready=0)
halon_monitor_ready 1
# TYPE halon_monitor_healthy gauge
# HELP halon_monitor_healthy The current health status (healthy=1, unhealthy=0)
halon_monitor_healthy 1
# TYPE halon_process_pid gauge
# HELP halon_process_pid Process PID
halon_process_pid 38098
....
```
### /ready
This endpoint returns a `200 OK` if the service is ready (started) otherwise a `5XX` error is returned.
It’s a one time state transition from unready to ready once the system is started.
It will not transition to unready again (unlike the `/healthy` endpoint).
Information regarding the ready status is provided in the OpenMetrics output as well as using the `halonctl process-stats` command.
### /healthy
This endpoint returns a `200 OK` in if the service is ready and healthy to accept mail otherwise a `5XX` error.
The response body of this request may give a clue about why a system is unhealty (but the output is not meant to be used).
The state may transition back and forth between unhealthy and healthy depending on the reason for the health/unheathly state.
Information regarding the health status is provided in the OpenMetrics output as well as using the `halonctl process-stats` command.
Reason for not being ready are.
* Service is not ready (see above)
* Service is shutting down
* Max messages affected by [`monitor.healthy.maxmessages.upper`](#config_smtpd:confval-monitor.healthy.maxmessages.upper) and [`monitor.healthy.maxmessages.lower`](#config_smtpd:confval-monitor.healthy.maxmessages.lower)
* Spool free bytes affected by [`monitor.healthy.spoolfreebytes.upper`](#config_smtpd:confval-monitor.healthy.spoolfreebytes.upper) and [`monitor.healthy.spoolfreebytes.lower`](#config_smtpd:confval-monitor.healthy.spoolfreebytes.lower)
* Spool free inodesbytes affected by [`monitor.healthy.spoolfreeinodes.upper`](#config_smtpd:confval-monitor.healthy.spoolfreeinodes.upper) and [`monitor.healthy.spoolfreeinodes.lower`](#config_smtpd:confval-monitor.healthy.spoolfreeinodes.lower)
* Manually set unhealthy (`halonctl health set --unhealthy` or [`HalonMTA_health_set()`](#plugins_native:c.HalonMTA_health_set))
If none of the conditions currently triggered, the system is healthy.
```bash
# halonctl process-stats |grep monitor
monitor.ready: true
monitor.healthy: true
```
# Control socket
The `smtpd` process has a control socket as specified by
[`environment.controlsocket.path`](#config_startup:confval-environment.controlsocket.path) or [`environment.controlsocket.port`](#config_startup:confval-environment.controlsocket.port).
All connections begin with a version handshake (two bytes, ASCII char 5 and 12 for version ‘6.12’), follow by one or more request and responses. All requests are sent beginning with a [command](#api_reference) identifier (one byte, ASCII char).
The [command](#api_reference) identifiers are assigned in alphabetical order.
In requests with a Protobuf [request](#api_reference) body, the [command](#api_reference) identifier is followed by
a binary host packed (LE) unsigned 64 bit integer telling the size of the request body.
The response begins with one byte indicating the status; `+` for success, or `E` for error.
It is followed by a binary host (LE) packed unsigned 64 bit integer
telling the size the response data that follows.
In the success case, the data is a Protobuf [response](#api_reference) body.
In the error case, it is an ASCII string.
# HTTP/JSON
Rather than connecting directly to the [control socket](#api_control_sockets),
there is a JSON over HTTP endpoint that proxies calls to the `smtpd` control socket. The JSON [request](#api_reference) body (called *payload*) and [response](#api_reference)
are direct JSON representations of the respective Protobuf structures.
The HTTP/JSON API takes *version*, *program*, *command* and *payload* as arguments,
where *payload* contains the [request](#api_reference) body.
* [Installation](#api_install)
* [Distributions](#api_install:distributions)
* [Ubuntu](#api_install:ubuntu)
* [RHEL](#api_install:rhel)
* [Azure Linux](#api_install:azure-linux)
* [Startup](#api_install:startup)
* [Upgrade](#api_upgrade)
* [Configuration](#api_config)
* [Authentication directives](#api_config:authentication-directives)
* [Listener directives](#api_config:listener-directives)
* [Other directives](#api_config:other-directives)
* [Environment directives](#api_config:environment-directives)
* [Examples](#api_examples)
# Installation
The HTTP/JSON API is installed using a Linux distribution package manager
according to the corresponding instructions, or by manually copying the files to the
`/opt/halon/api` directory.
In preparation; you should make sure that you have already installed the `smtpd` package
and [copied](#install:comp-install-conf) the configuration files for those [programs](#install:comp-programs) into the `/etc/halon/` directory.
## Distributions
### Ubuntu
To install the .deb package use the following commands.
```console
# apt-get update
# apt-get install halon-api
```
### RHEL
To install the .rpm package use the following commands.
```console
# yum install halon-api
```
### Azure Linux
To install the .rpm package use the following commands.
```console
# tdnf install -y halon-api
```
## Startup
When the initial [configuration](#api_config) is in place, the program can be started with:
```console
# systemctl start halon-api
```
# Upgrade
In order to upgrade the package, install the new package over the previous installed one (see the installation chapter).
After the upgrade the service will be stopped and requires to be manually started.
# Configuration
*halon-api* loads the non-reloadable configuration from `/etc/halon/api.yaml`.
It is described by, and can be validated with, the
[api.schema.json](http://lbovet.github.io/docson/index.html#https://docs.halon.io/json-schemas/api/6.12-stable/api.schema.json)
[JSON schema](https://docs.halon.io/json-schemas/api/6.12-stable/) (included in our [Visual Studio Code integration](#vscode)).
The default startup configuration file in `/opt/halon/api/share` can normally be used as a base:
```console
# cp /opt/halon/api/share/api.yaml /etc/halon/
```
## Authentication directives
### authentication.secret
The secret that needs to be sent in the request using the *X-API-Key* header.
## Listener directives
### listeners[]
The API must have one or more listen directives,
which specify which TCP port and address to listen on. **Required.**
### listeners[].port
Which TCP port to listen on. **Required.**
### listeners[].address
IPv4 or IPv6 address to listen on.
The default is to listen to all IPv4 and IPv6 addresses.
### listeners[].pki
The X.509 certificate and private key.
The private key and certificate should have either a `path` or `data` property.
```yaml
pki:
certificate:
data: |-
-----BEGIN CERTIFICATE-----
...
privatekey:
data: |-
-----BEGIN PRIVATE KEY-----
...
```
### listeners[].maxsize
The maximum request body size to accept. The default is 52 428 800 bytes.
## Other directives
### accounting
If accounting logs should be generated. The default is *false*.
## Environment directives
### environment.privdrop.user
The user to run the process as.
### environment.privdrop.group
The group to run the process as.
### environment.controlsockets.smtpd.path
See [`environment.controlsocket.path`](#config_startup:confval-environment.controlsocket.path).
### environment.controlsockets.smtpd.port
See [`environment.controlsocket.port`](#config_startup:confval-environment.controlsocket.port).
### environment.controlsockets.smtpd.address
See [`environment.controlsocket.address`](#config_startup:confval-environment.controlsocket.address).
### environment.controlsockets.smtpd.tls
The TLS settings to use when connecting to the `smtpd` control socket. The default is no TLS.
### environment.controlsockets.smtpd.tls.certificate
The client certificate to use. This setting should have either a `path` or `data` property.
### environment.controlsockets.smtpd.tls.privatekey
The private key for the client certificate. This setting should have either a `path` or `data` property.
### environment.controlsockets.smtpd.tls.verify
If the server peer certificate should be verified. The default is `true`.
### environment.controlsockets.smtpd.tls.verifyname
Hostname to verify against the server certificate’s CN and SAN.
### environment.controlsockets.rated.path
See the [rated](https://docs.halon.se/rated/) manual.
### environment.controlsockets.dlpd.path
See the [dlpd](https://docs.halon.se/dlpd/) manual.
# Examples
*Simple example without request body (payload)*
```console
% curl https://mta1/api/protobuf -d '{ "version": { "major": 6, "minor": 8 }, "program": "smtpd", "command": "q" }' \
-H "Content-Type: application/json" \
-H "X-API-Key: badsecret"
{
"process": {
"pid": "145",
"runtime": "4235"
},
...
```
*More complex JSON request that returns queued email from example.org or example.com*
```json
{
"version": { "major": 6, "minor": 8 },
"program": "smtpd",
"command": "F",
"payload": {
"conditions": {
"senders": [
{ "domain": { "value": "example.org" }},
{ "domain": { "value": "example.com" }}
]
}
}
}
```
The [command line interface](#cli) has two arguments called
`--json-request` and `--json` which dumps the [request](#api_reference) and [response](#api_reference) Protobuf
data as JSON. It can be very helpful when working with the HTTP/JSON API.
*Using halonctl to learn about the [request](#api_reference) and [response](#api_reference) data format*
```console
$ halonctl queue update --bounce --state DEFER --jobid foobar --json-request --json
{
"conditions": {
"queues": [
{
"queue": "DEFER"
...
```
# API reference
The API request and response bodies are defined by the [Protocol Buffer schemas](https://docs.halon.io/protobuf-schemas/6.12-stable/).
The following section describes them in more detail.
## Command list
This is a list of all the API commands; which program that implements them,
their command identifier, and request/response body format.
| Command | Request | Response | Description |
|-----------|-------------------------------|--------------------------------|-----------------------------------------------------------------------------------------|
| a | ConfigGreenDeployRequest | | [Live stage](#cli_config:cli-bluegreen) |
| b | | ConfigGreenStatusResponse | ” (status) |
| c | | | ” (cancel) |
| e | | HSLBreakPointResponse | Breakpoint |
| f | | | ” (continue) |
| g | | HSLCacheResponse | Script [cache](#cli_script:cli-cache) |
| h | HSLCacheClearRequest | HSLCacheClearResponse | ” (clear) |
| y | HSLMemoryRequest | HSLMemoryResponse | Script [memory](#cli_script:cli-shm) |
| K | HSLMemoryListRequest | HSLMemoryListResponse | ” (list) |
| z | HSLMemoryStoreRequest | HSLMemoryStoreResponse | ” (store) |
| A | HSLMemoryDeleteRequest | HSLMemoryDeleteResponse | ” (delete) |
| i | | ConfigResponse | Contains parts of the running configuration |
| j | HealthRequest | | Set the system health |
| l | ResolverQueueListRequest | ResolverQueueListResponse | Get the list of queued DNS queries |
| m | QueueAnalyzeRequest | QueueAnalyzeResponse | The the queue analyze output |
| r | | | [Reload](#cli_config:cli-reload) config |
| o | | | ” (suspend) |
| p | | | ” (policy) |
| H | | | ” (delivery) |
| s | SuspendRequest | SuspendResponse | [Queue suspend](#cli_queue:cli-queue-suspend) |
| t | SuspendAddRequest | SuspendAddResponse | ” (add) |
| O | SuspendUpdateRequest | SuspendUpdateResponse | ” (update) |
| u | SuspendDeleteRequest | SuspendDeleteResponse | ” (delete) |
| v | PolicyConditionRequest | PolicyConditionResponse | [Queue policy](#cli_queue:cli-queue-policy) |
| w | PolicyConditionAddRequest | PolicyConditionAddResponse | ” (add) |
| P | PolicyConditionUpdateRequest | PolicyConditionUpdateResponse | ” (update) |
| x | PolicyConditionDeleteRequest | PolicyConditionDeleteResponse | ” (delete) |
| B | PolicyRateRefillRequest | PolicyRateRefillResponse | ” (rate refill) |
| D | QueueGroupByRequest | QueueGroupByResponse | [Queue distr.](#cli_queue:cli-queue-groupby) |
| F | QueueListRequest | QueueListResponse | [Queue list](#cli_queue:cli-queue-list) |
| G | QueueUpdateRequest | QueueUpdateResponse | [Queue update](#cli_queue:cli-queue-update) |
| E | QueueImportRequest | QueueImportResponse | Queue import |
| J | QueueUnloadRequest | QueueUnloadResponse | [Queue unload](#cli_queue:cli-queue-unload) |
| I | QueueQuotaRequest | QueueQuotaResponse | [Queue quota](https://docs.halon.io/hsl/archive/6.12-stable/functions.html#queue_quota) |
| q | | ProcessStatsResponse | Show [stats](#cli_stats:cli-stats) |
| C | ResolverCacheClearRequest | ResolverCacheClearResponse | Clear [DNS](#cli_dns:cli-dns) |
| L | ServerConnectionsListRequest | ServerConnectionsListResponse | |
| M | ServerConnectionsCloseRequest | ServerConnectionsCloseResponse | |
| N | PluginCommandRequest | PluginCommandResponse | |
| Q | QueueExportRequest | \ | Export a queued message’s content. The response is not an protobuf object. |
| V | | VersionResponse | |
| X | TraceSMTPRequest | \ | Send a message trace. The response is not an protobuf object. |
| T | PolicyMatchRequest | PolicyMatchResponse | |
| R | QueueConnectionsListRequest | QueueConnectionsListResponse | |
| S | QueueConnectionsCloseRequest | QueueConnectionsCloseResponse | |
| U | PolicyGroupingRequest | PolicyGroupingResponse | |
| W | PolicyGroupingListRequest | PolicyGroupingListResponse | |
| Y | QueueUpdateStatusRequest | QueueUpdateStatusResponse | |
| Z | ResolverQueryListRequest | ResolverQueryListResponse | Get the list of running DNS queries |
# Version compatibility
The protobuf API is versioned, below you will find the version compatibility for different versions.
| Version | Min |
|-----------|-------|
| 6.12 | 5.6 |
| 6.11 | 5.6 |
| 6.10 | 5.6 |
| 6.9 | 5.6 |
| 6.8 | 5.6 |
| 6.7 | 5.6 |
| 6.6 | 5.6 |
| 6.5 | 5.6 |
| 6.4 | 5.6 |
| 6.3 | 5.6 |
| 6.2 | 5.6 |
| 6.1 | 5.6 |
| 6.0 | 5.6 |
| 5.13 | 5.6 |
| 6.12 | 5.6 |
| 5.11 | 5.6 |
| 5.10 | 5.6 |
| 5.9 | 5.6 |
| 5.8 | 5.6 |
| 5.7 | 5.6 |
| 5.6 | 5.6 |
| 5.5 | 5.3 |
| 5.4 | 5.3 |
| 5.3 | 5.3 |
| 5.2 | 5.2 |
# HTTP submission
Halon supports message submission through a HTTP API. This HTTP API allows you to
submit messages directly to the EOD data hook. This API should be used to submit
messages generated from a message generation platform, only doing slight modifications
such as adding headers, or performing DKIM signing etc. HTTP submission endpoints are
configured as servers in the [startup configuration](#config_startup).
> **Note**
>
> If you need something more powerful that supports message generation and text substitutions
> using the [Handlebars](https://handlebarsjs.com) templating language as well as user engagement tracking
> see our [extended HTTP submission](https://docs.halon.io/submission) API.
## Endpoint reference
The only endpoint currently available on the HTTP API is `/v1/submission`.
### Authentication
Authentication is done by using a `X-API-Key` HTTP header.
### HTTP request format
Message injection is done by sending a POST request with two named `multipart/form-data` parts.
Optional gzip compression is supported, if the parts are named `options.gzip` and `rfc822.gzip` respectively.
- *options* (application/json)
> This should be a JSON object containing sender and recipients information
> as well as an optional `metadata` properties (this properties appears JSON decoded in the EOD hook as `$arguments["metadata"]`).
> The `sender` and `recipients` domain support internationalized domain names (U-labels).
>
> ```JSON
> {
> "sender": { "localpart": "sender", "domain": "example.com" },
> "recipients": [
> {"localpart": "recipient1", "domain": "example.org" },
> {"localpart": "recipient2", "domain": "example.org" }
> ],
> "metadata": { "jobid": "xyz" }
> }
> ```
- *rfc822* (message/rfc822)
> This part should be a RFC822 formatted message.
>
> ```default
> Subject: Hello
>
> Hello World
> ```
### HTTP response format
On success a JSON object with the result will be returned. The output is not pretty printed unless the query string *?pretty=true* is given.
> ```JSON
> {
> "result": {
> "code": 250,
> "enhanced": [
> 2,
> 0,
> 0
> ],
> "reason": [
> "Ok: queued as 7c9e67bb-661f-4ccb-b8ee-521a0e723324"
> ]
> },
> "transaction": {
> "id": "7c9e67bb-661f-4ccb-b8ee-521a0e723324"
> },
> "metadata": {
> "foo": "bar"
> }
> }
> ```
## Examples
This is an example how to use curl to inject a message.
```console
$ curl --request POST \
--header "X-API-Key: badsecret" \
--form "options=@sample.json;type=application/json" \
--form "rfc822=@sample.eml;type=message/rfc822" \
http://127.0.0.1:80/v1/submission?pretty
```
# Web administration
There are three primary ways of managing hosts (or clusters of);
the web administration, API ([Protocol Buffer](https://docs.halon.io/protobuf-schemas/6.12-stable/)) or [CLI](#cli).
This section is a guide for the web administration.
> **Note**
>
> The documentation for the web administration has been moved [here](https://docs.halon.io/web).
# Code editor integrations
We have created integrations for working with the [configuration](#config) using the code editors listed below.
* [Visual Studio Code](#vscode)
* [Extensions](#vscode:extensions)
* [Configuration Packer](#vscode:configuration-packer)
* [HSL Syntax](#vscode:hsl-syntax)
* [HSL Linter](#vscode:hsl-linter)
* [HSL Debugger](#vscode:hsl-debugger)
* [Code Companion](#vscode:code-companion)
* [Providing feedback](#vscode:providing-feedback)
* [Working with Dev Containers and Remote SSH](#vscode:working-with-dev-containers-and-remote-ssh)
* [Limitations](#vscode:limitations)
* [Copilot error messages](#vscode:copilot-error-messages)
* [Setting up a development environment](#vscode:setting-up-a-development-environment)
* [Editing YAML with custom tags](#vscode:editing-yaml-with-custom-tags)
* [Vim](#vim)
* [Features](#vim:features)
* [Installation](#vim:installation)
* [Upgrade](#vim:upgrade)
* [Screenshots](#vim:screenshots)
# Visual Studio Code
Halon has a fully-featured integration with [Visual Studio Code](https://code.visualstudio.com) that leverages [Visual Studio Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview)
which allows you to connect to a container or remote machine (where Halon is installed) to work with it’s [configuration](#config),
including [linting](#vscode:vscode-linter) and [debugging](#vscode:vscode-debug) it’s Halon [script](https://docs.halon.io/hsl/) (HSL) source files.
## Extensions
Below is a list of the extensions we provide and whether they should be installed [locally or remotely](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds).
If both options are marked it can be installed either [locally or remotely](https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds) depending on the use case.
| Extension | Local | Remote | Marketplace |
|----------------------------------------------------------------------------------|---------|----------|--------------------------------------------------------------------------------------------|
| [Halon Configuration Packer](#vscode:vscode-conf) | ✓ | ✓ | [Install](https://marketplace.visualstudio.com/items?itemName=Halon.vscode-halon) |
| [Halon Scripting Language Syntax](#vscode:vscode-syntax) | ✓ | | [Install](https://marketplace.visualstudio.com/items?itemName=Halon.hsl-syntax) |
| [Halon Scripting Language Linter](#vscode:vscode-linter) | | ✓ | [Install](https://marketplace.visualstudio.com/items?itemName=Halon.hsl-linter) |
| [Halon Scripting Language Debugger](#vscode:vscode-debug) | | ✓ | [Install](https://marketplace.visualstudio.com/items?itemName=Halon.hsl-debug) |
| [Halon Code Companion](#vscode:vscode-code-companion) | ✓ | ✓ | [Install](https://marketplace.visualstudio.com/items?itemName=Halon.vscode-code-companion) |
### Configuration Packer
Provides a [configuration packer](#config_smtpd:config-script) for Halon.
It also provides a linter for the [configuration](#config) files.
> **Note**
>
> This extension can be installed either locally or remotely depending on the use case.
> When installed locally it can also be used to quickly [setup a development environment](#vscode:vscode-dev).
Run the `Halon: Build` command to package the configuration files.
If you don’t have an existing configuration you can run the `Halon: Init` command and select a configuration template.

*The autocomplete and linter for the YAML [configuration](#config) files in action*
### HSL Syntax
Provides syntax highlighting for Halon [script](https://docs.halon.io/hsl/) (HSL) source files.

*The Halon [script](https://docs.halon.io/hsl/) (HSL) syntax highlighting in action*
### HSL Linter
Provides a linter for Halon [script](https://docs.halon.io/hsl/) (HSL) source files.
> **Note**
>
> This extension should only be installed remotely.

*The Halon [script](https://docs.halon.io/hsl/) (HSL) linter in action*
### HSL Debugger
Provides a [debugger](https://code.visualstudio.com/docs/editor/debugging) for Halon [script](https://docs.halon.io/hsl/) (HSL) source files.
> **Note**
>
> This extension should only be installed remotely.
[Launch configuration](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) snippets are available under the *Halon Scripting Language* namespace when the extension has been installed.
Use the *Debug File* snippet if you want to debug a HSL module inside the `src/files` folder using our [script interpreter](#cli_hsh:hsh) or the *Debug Live Stage* snippet if you want to debug the [smtpd](#install:comp-programs) process when using [blue-green testing](#cli_config:cli-bluegreen).
If you open the `.vscode/launch.json` file that gets created you can add `conditions` to the *Debug Live Stage* snippet to limit which incoming connections the test configuration gets applied for, such as `probability` or `remoteips`:
```json
{
"name": "Debug Live Stage",
"type": "halon",
"request": "launch",
"conditions": {
"probability": 0.01,
"remoteips": [
"10.2.55.20"
]
}
}
```

*The Halon [script](https://docs.halon.io/hsl/) (HSL) debugger in action*
### Code Companion
Code Companion provides powerful machine-learning driven code generation and analysis for Halon Scripting Language (HSL) [script](https://docs.halon.io/hsl/) source files.
It extends the capabilities of [GitHub Copilot](https://github.com/features/copilot/) and provides a chat interface to ask for code suggestions, explanations, and reviews.
Before starting, ensure you have [GitHub Copilot](https://github.com/features/copilot/) installed in your Visual Studio Code. The free version is available for all users.
Open Copilot chat using the  icon in the top line of the editor window.
Alternatively, you can use the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) and search for *Copilot: Open Chat*.
Open an HSL file in the main code window and start typing your prompt in the Copilot chat.
You should see the file appears in a box, known as *context*.

*Copilot chat with the Halon extension active*
Prefix your question to Copilot with `@halon`, this is known as *mentioning* or *tagging*. If the extension is active, the text will be shown in blue.
You can drag more files into the chat window to give more context. This can include HSL and YAML files.
The response from Copilot should start with the “Halon Code Companion” logo and title:

*Copilot response with the Halon extension active*
If you don’t see this icon, you are getting responses **without the Halon extension**, which may give misleading results based on other programming languages.
Example queries you can ask:
Adding code to a module
: `@halon add DKIM signing to this module`
Explain the purpose of code
: `@halon explain this code`
Look for improvements to code
: `@halon review this code`
Responses can take a few seconds to appear. For responses to add or modify code, the chat window will contain suggested code in a panel.
You can then copy and paste this manually, but it is easier to use the “Apply in Editor”  button. which appears when you hover over the example code panel.

*Buttons to apply code suggestions into editor file*
This prompts you to accept changes into your current file.
Changes are shown with red/green color highlighting, and you can accept or reject each change individually.

*Accept/reject/view diffs in editor file*
For more information, see [VSCode Copilot documentation](https://code.visualstudio.com/docs/copilot/copilot-chat).
#### Providing feedback
There is an easy way to provide feedback on Code Companion suggestions using the thumbs-up or thumbs-down icons.
For thumbs-down, you can provide a reason why it was unhelpful.

*Feedback*
As this feedback mechanism does not include specifics of your code or problem, please open a [support](https://halon.io/support) ticket directly to Halon.
#### Working with Dev Containers and Remote SSH
When working in a Dev Container, or using [Remote SSH](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh), ensure that the Code Companion extension is installed remotely as well as locally.
This can be done via the left panel Extensions menu. If this is not done, you are querying “raw” Copilot *not the Halon extension*.
#### Limitations
* If you see example “autocomplete” code suggestions appearing as gray text while you type in your main code window, these come from “raw” Copilot *not via the Halon extension*, so may not be correct HSL.
* The Copilot “Editor inline chat” feature accessible via right-click in an editor window does not use the Halon extension, so is not recommended for HSL work.
#### Copilot error messages
If the files providing context are too long, you may see an error message such as `Token budget of 63802 exceeded by 1743`. You can try to reduce the number of files or the size of the files.
## Setting up a development environment
To quickly setup a development environment using a container and [Docker Desktop](https://www.docker.com/products/docker-desktop), simply install the [Halon Configuration Packer](#vscode:vscode-conf) extension locally,
open a new empty folder, run the `Halon: Init` command and when the selection for *Remote development* comes up, pick the *container* option.
After this, follow the instructions in the `README.md` file that opens up to get started.
See [Developing inside a Container](https://code.visualstudio.com/docs/remote/containers) for more information about how this works.
## Editing YAML with custom tags
Halon configuration can make use of custom tags that correspond to data types, such as `!Map` and `!Set`.
Note that HSL can also apply specific data types in the [import .. with](https://docs.halon.io/hsl/structures.html#import) statement.
Custom tags will trigger syntax warnings in your editor, but this can be solved by configuring `customTags` for the [Red Hat YAML plugin](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) in `.vscode/settings.json`.
Here is a typical setup for working with Halon YAML:
*.vscode/settings.json*
```json
{
"yaml.customTags": [
"!Map mapping",
"!Set sequence",
"!Regex scalar",
"!PrivateKey scalar",
"!X509 scalar",
"!MailMessage scalar",
"!Env scalar"
]
}
```
# Vim
We have created a [Vim](https://www.vim.org) plugin to make it easier to work with our [configuration](#config).
It provides syntax highlighting, indentation and completion for the Halon [script](https://docs.halon.io/hsl/) (HSL) source files.
> > **Note**
> >
> > For a more fully-featured integration that supports [linting](#vscode:vscode-linter) and [debugging](#vscode:vscode-debug)
> > right inside the code editor, see our [Visual Studio Code integration](#vscode) instead.
## Features
* Syntax highlighting
* Indentation
* Completion
* Covers the entire standard library
## Installation
To install the plugin use the following command.
```console
$ git clone https://github.com/halon/vim-hsl.git ~/.vim/pack/vendor/start/vim-hsl
```
To enable it add the following to your `~/.vimrc` file.
```default
syntax on
filetype plugin indent on
```
### Upgrade
In order to upgrade the plugin, use the following command.
```console
$ git -C ~/.vim/pack/vendor/start/vim-hsl pull
```
## Screenshots

*The Halon [script](https://docs.halon.io/hsl/) (HSL) syntax highlighting and completion in action*

*Linting a script hook in a Vim window with `:terminal halonconfig`*
# Plugins
This section describes methods for extending the functionality of the MTA by loading C-compatible libraries written in programming languages other than Halon [script](https://docs.halon.io/hsl/) (HSL).
These are:
* The **FFI** class allows loading libraries directly from Halon [script](https://docs.halon.io/hsl/) without modifying the library’s source code or API.
* The **HSL plugin** system allows you to natively extend HSL by creating special libraries which registers HSL functions.
* The **Queue** system allows you to easily implement custom delivery policies (such as automatic IP warmup) for the [queue](#queue).
* The **Delivery plugin** system allows you to easily implement custom delivery methods (such as HTTP) for the [queue](#queue).
* [FFI](#plugins_ffi)
* [Native plugins](#plugins_native)
* [Exported by plugin](#plugins_native:exported-by-plugin)
* [Command plugin](#plugins_native:command-plugin)
* [HSL plugin](#plugins_native:hsl-plugin)
* [Queue plugin](#plugins_native:queue-plugin)
* [Delivery plugin](#plugins_native:delivery-plugin)
* [Exported by MTA](#plugins_native:exported-by-mta)
* [Objects](#plugins_native:objects)
* [Callable](#plugins_native:callable)
* [HSL plugin](#plugins_native:id1)
* [Lists plugin](#plugins_native:lists-plugin)
* [Inject plugin](#plugins_native:inject-plugin)
* [Import plugin](#plugins_native:import-plugin)
* [Queue plugin](#plugins_native:id4)
* [Delivery plugin](#plugins_native:id5)
* [Monitor plugin](#plugins_native:monitor-plugin)
* [HSL plugins](#plugins_hsl)
* [Development](#plugins_development)
* [hsh](#plugins_development:hsh)
* [hsh.yaml](#plugins_development:hsh-yaml)
* [hsh-app.yaml](#plugins_development:hsh-app-yaml)
* [Examples](#plugins_examples)
# FFI
The [FFI](https://docs.halon.io/hsl/functions.html#FFI) system allows you to load C-compatible libraries and call their functions from Halon [script](https://docs.halon.io/hsl/) (HSL).
It supports most of the C ABI, including basic data types, references, pointers, callback functions, and more.
The method of using FFI to extend HSL (or any other language) is often the preferred method when working with an already existing library.
To learn how to use FFI, see the Halon script manual’s [FFI](https://docs.halon.io/hsl/functions.html#FFI) class section.
> **Note**
>
> The [`scripting.ffi`](#config_startup:confval-scripting.ffi) setting needs to be enabled before you can start using the [FFI](https://docs.halon.io/hsl/functions.html#FFI) class.
# Native plugins
There are multiple types of native plugins that can be built working with Halon [script](https://docs.halon.io/hsl/) (HSL), queue, policies, suspensions and delivery.
They should be written as C-compatible libraries (.so).
It is possible to build plugins in any language able to provide a C ABI interface.
Examples of such languages are C, C++, Rust and the Go language (cgo).
Native plugins are loaded into the `smtpd` process from the configuration file’s [`plugins[]`](#config_startup:confval-plugins) section.
HSL plugins may be developed and tested using the [hsh](#cli_hsh:hsh) program (see the `--plugin` option).
Plugins may also be auto-loaded if added to the */opt/halon/plugins* folder and symlinked from */opt/halon/plugins/autoload/smtpd* and */opt/halon/plugins/autoload/hsh* folders (may needs to be created).
All functions, defines and types (opaque pointers) used when building the plugin is defined in the `HalonMTA.h` header file (installed in */opt/halon/include*).
There is a distinction between functions that should be implemented by the library
and functions that are provided by the caller (MTA).
Our naming convention is that all functions that should be provided by the library starts with *Halon_* and
all functions that are provided by the MTA *HalonMTA_*.
## Exported by plugin
The following functions may be applicable to all plugins.
### int Halon_version()
This is the only required function. It must return the value of HALONMTA_PLUGIN_VERSION from the HalonMTA.h header file.
```c
HALON_EXPORT
int Halon_version()
{
return HALONMTA_PLUGIN_VERSION;
}
```
### bool Halon_init(HalonInitContext \*hic)
This function is called by the MTA upon startup. If this function returns false then the MTA will not start.
It should be used for initialization on the plugin. The `hic` argument holds a reference to the init context (which can be used to access the configuration)
of the plugin, and it is only valid for the duration of the `Halon_init` call.
Use the [`HalonMTA_init_getinfo()`](#plugins_native:c.HalonMTA_init_getinfo) functions with the `hic` argument.
```c
HALON_EXPORT
bool Halon_init(HalonInitContext* hic)
{
return true;
}
```
### bool Halon_early_init(HalonInitContext \*hic)
This function is called by the MTA upon startup. If this function returns false then the MTA will not start.
It is called before [`Halon_init()`](#plugins_native:c.Halon_init), and before any suspends or policies are loaded (so that it can create queue [lists](#config_smtpd:config-list) using [`HalonMTA_queue_list_add()`](#plugins_native:c.HalonMTA_queue_list_add)).
For all other use-cases [`Halon_init()`](#plugins_native:c.Halon_init) should be used.
```c
HALON_EXPORT
bool Halon_early_init(HalonInitContext* hic)
{
return true;
}
```
### bool Halon_privileged_init(HalonInitContext \*hic)
This function is called by the MTA upon startup. If this function returns false then the MTA will not start.
It is called before [`Halon_early_init()`](#plugins_native:c.Halon_early_init) and should be used to eg. bind privileged socket etc.
For all other use-cases [`Halon_early_init()`](#plugins_native:c.Halon_early_init) or [`Halon_init()`](#plugins_native:c.Halon_init) should be used.
```c
HALON_EXPORT
bool Halon_privileged_init(HalonInitContext* hic)
{
return true;
}
```
### void Halon_ready(HalonReadyContext \*hrc)
This function is called by the MTA just before entering ready state. All internal processes should have been started.
```c
HALON_EXPORT
void Halon_ready(HalonReadyContext* cfg)
{
}
```
### void Halon_config_reload(HalonConfig \*hc)
This function is called by the MTA upon configuration reloads (*smtpd-app.yaml*).
It can be used to reload the configuration of the plugin. The `hc` argument holds a reference to the configuration of the plugin, and it is only valid for the duration of the `Halon_config_reload` call.
Use the [`HalonMTA_config_object_get()`](#plugins_native:c.HalonMTA_config_object_get) family of functions to work with the configuration.
```c
HALON_EXPORT
void Halon_config_reload(HalonConfig* cfg)
{
}
```
### void Halon_cleanup()
This function is called by the MTA upon shutdown. It should be used for cleaning before unloading the plugin.
For example closing connections and joining any threads created by the plugin.
This callback is called right before the process is terminated.
```c
HALON_EXPORT
void Halon_cleanup()
{
}
```
### void Halon_early_cleanup()
This function is called early in the cleanup by the MTA upon shutdown. It’s called as one of the first things in the shut down process.
For example stopping a custom message injector.
```c
HALON_EXPORT
void Halon_early_cleanup()
{
}
```
### Command plugin
#### bool Halon_command_execute(HalonCommandExecuteContext \*hcec, size_t argc, const char \*argv[], size_t argvl[], char \*\*out, size_t \*outlen)
This function is called by the MTA when a plugin command request (over protobuf) is directed to this plugin (based on its id). If this function
returns false, then `out` is considered an error message. Otherwise it’s arbitrary successful data (that can be encoded in any format). The `out` data memory will
be owned by the MTA.
```c
HALON_EXPORT
bool Halon_command_execute(HalonCommandExecuteContext* hcec, size_t argc, const char* argv[], size_t argvl[], char** out, size_t* outlen)
{
if (argc > 0 && strcmp(argv[0], "hello") == 0)
{
*out = strdup("world");
return true;
}
return false;
}
```
#### bool Halon_plugin_command(const char \*in, size_t inlen, char \*\*out, size_t \*outlen)
Deprecated in 5.8 by [`Halon_command_execute()`](#plugins_native:c.Halon_command_execute)
This function is called by the MTA when a plugin command request (over protobuf) is directed to this plugin (based on its id). If this function
returns false, then `out` is considered an error message. Otherwise it’s arbitrary successful data (that can be encoded in any format). The `out` data memory will
be owned by the MTA.
### HSL plugin
The following functions are applicable to HSL plugins only (not delivery plugins).
HSL plugins are preferred over [FFI](https://docs.halon.io/hsl/functions.html#FFI) when writing a new library, specific to Halon.
#### bool Halon_hsl_register(HalonHSLRegisterContext \*hhrc)
This method is called by the MTA and tooling ([hsh](#cli_hsh:hsh) and `hsl-lint`) to register all the HSL functions provided by the plugin.
The `hhrc` argument should be used when calling the [`HalonMTA_hsl_register_function()`](#plugins_native:c.HalonMTA_hsl_register_function) function. On error false is returned.
```c
HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
HalonMTA_hsl_register_function(hhrc, "myfunc", myfunc);
return true;
}
```
#### void myfunc(HalonHSLContext \*hhc, HalonHSLArguments \*args, HalonHSLValue \*ret)
This is the signature of the function that will be called from HSL. It takes three arguments:
* *hhc* is a pointer to the current execution context. This can be used to control execution of the plugin.
* *args* is the list of function arguments when calling the function from HSL.
* *ret* a reference to the return value (which can be modified). Its default value is *None*.
The lifetime of the *hhc*, *args* and *ret* argument pointers are only valid during the call of the *myfunc* function.
```c
static void myfunc(HalonHSLContext* hhc, HalonHSLArguments* args, HalonHSLValue* ret)
{
double n = 123.456;
HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_NUMBER, &n, 0);
}
```
### Queue plugin
The queue plugin functions are called when a message is picked up from the active queue in order to be able to acquire or account for delivery slots,
and also when a delivery is done and the slots should be released (eg. when a estimated concurrency for a destination should be increased or decreased).
#### bool Halon_queue_insert_callback(HalonQueueContext \*hqc)
Get or set information on a queue message before adding to the active queue. If this function return false, the message is sent back to the pre-delivery hook.
#### bool Halon_queue_pickup_acquire(HalonQueueMessage \*hqm)
This method is called by the MTA when a message is picked up from the active queue for delivery.
If you need the message’s pickup parameters, use the [`HalonMTA_message_getinfo()`](#plugins_native:c.HalonMTA_message_getinfo) function.
This function must be non-blocking.
If this function return false, the message is not to be delivered, instead it will be requeued to run the
pre-delivery hooks and the [`Halon_queue_pickup_release()`](#plugins_native:c.Halon_queue_pickup_release) will not be called.
If the message is requeued the concurrency and rate is not incremented.
#### bool Halon_queue_pickup_acquire2(HalonQueueContext \*hqc)
This method is called by the MTA when a message is picked up from the active queue for delivery.
If you need the message’s pickup parameters, use the [`HalonMTA_queue_getinfo()`](#plugins_native:c.HalonMTA_queue_getinfo) function and get *HALONMTA_INFO_MESSAGE* and then use the [`HalonMTA_message_getinfo()`](#plugins_native:c.HalonMTA_message_getinfo) function.
If you need to pass data to the pre or post-delivery hook use the [`HalonMTA_queue_getinfo()`](#plugins_native:c.HalonMTA_queue_getinfo) function and get *HALONMTA_INFO_RETURN* properties.
This function must be non-blocking.
If this function return false, the message is not to be delivered, instead it will be requeued to run the
pre-delivery hooks and the [`Halon_queue_pickup_release()`](#plugins_native:c.Halon_queue_pickup_release) will not be called.
If the message is requeued the concurrency and rate is not incremented.
#### void Halon_queue_pickup_release(HalonQueueMessage \*hqm)
This method is called by the MTA when a message’s delivery attempt is done.
If you need the message’s pickup parameters, use the [`HalonMTA_message_getinfo()`](#plugins_native:c.HalonMTA_message_getinfo) function.
This function must be non-blocking.
#### void Halon_queue_pickup_release2(HalonQueueContext \*hqm)
This method is called by the MTA when a message’s delivery attempt is done.
If you need the message’s pickup parameters, use the [`HalonMTA_queue_getinfo()`](#plugins_native:c.HalonMTA_queue_getinfo) function and get *HALONMTA_INFO_MESSAGE* and then use the [`HalonMTA_message_getinfo()`](#plugins_native:c.HalonMTA_message_getinfo) function.
If you need to pass data to the post-delivery hook use the [`HalonMTA_queue_getinfo()`](#plugins_native:c.HalonMTA_queue_getinfo) function and get *HALONMTA_INFO_RETURN* properties.
This function must be non-blocking.
### Delivery plugin
The deliver plugin system provides an interface for custom, non-blocking, non-SMTP delivery methods.
It simply replaces the [queue](#queue)’s regular SMTP/LMTP client with a custom sending routine.
In other words, it sits in between the active queue pickup and the post-delivery script.
#### void Halon_deliver(HalonDeliverContext \*hdc)
This method is called by the MTA when sending using a plugin for delivery.
This function must be non-blocking.
```c
HALON_EXPORT
void Halon_deliver(HalonDeliverContext* hdc)
{
short int status = 250;
HalonMTA_deliver_setinfo(hdc, HALONMTA_RESULT_CODE, &status, 0);
HalonMTA_deliver_setinfo(hdc, HALONMTA_RESULT_REASON, "OK", 0);
HalonMTA_deliver_done(hdc);
}
```
## Exported by MTA
These are functions provided by the MTA.
### bool HalonMTA_init_getinfo(HalonInitContext \*hic, int type, const void \*inval, size_t inlen, void \*outval, size_t \*outlen)
Get info about the plugin (from within the Halon_init function). The following types are available. No memory is owned by the caller. For most types
inval and inlen should be (NULL and 0).
On error false is returned.
| Type | inval | inlen | outval | outlen |
|-------------------------|---------|---------|----------------|----------|
| HALONMTA_INIT_CONFIG | NULL | 0 | &HalonConfig\* | n/a |
| HALONMTA_INIT_APPCONFIG | NULL | 0 | &HalonConfig\* | n/a |
```c
HALON_EXPORT
bool Halon_init(HalonInitContext* hic)
{
HalonConfig* cfg;
HalonMTA_init_getinfo(hic, HALONMTA_INIT_APPCONFIG, nullptr, 0, &cfg, nullptr);
}
```
### HalonConfig \*HalonMTA_config_array_get(HalonConfig \*cfg, size_t index)
Get a pointer reference to the index value of an array.
If the `cfg` argument is not an array or the `index` is out of bound then NULL is returned.
The pointer returned from this function must not be freed.
```c
size_t index = 0;
HalonConfig* val;
while ((val = HalonMTA_config_array_get(cfg, index++)))
/* work with val */;
```
### HalonConfig \*HalonMTA_config_object_get(HalonConfig \*cfg, const char \*property)
Get a pointer reference to the property value of a map.
If the `cfg` argument is not a map or the `property` doesn’t exist then NULL is returned.
The pointer returned from this function must not be freed.
```c
HalonConfig* value = HalonMTA_config_object_get(cfg, "key");
```
### HalonConfig \*HalonMTA_config_object_key_get(HalonConfig \*cfg, size_t index)
Get a pointer reference to the property value of an object at the specified index.
If the `cfg` argument is not an object or the `index` is out of bound then NULL is returned.
The pointer returned from this function must not be freed.
```c
size_t index = 0;
HalonConfig* property;
while ((property = HalonMTA_config_object_key_get(cfg, index++)))
/* work with property */;
```
### const char \*HalonMTA_config_string_get(HalonConfig \*cfg, size_t \*outlen)
Get a pointer reference to the scalar value of `cfg`.
If the `cfg` argument is not a scalar then NULL is returned.
The pointer returned from this function must not be freed.
```c
HalonConfig* value = HalonMTA_config_object_get(cfg, "key");
if (value)
{
const char* str = HalonMTA_config_string_get(value, NULL);
if (str)
printf("%s\n", str);
}
```
### bool HalonMTA_config_to_json(const HalonConfig \*value, char \*\*out, size_t \*outlen)
Convert a HalonConfig to a JSON string. If the conversion fails, false is returned and out contains the error message (if any; otherwise null).
The memory returned in out must be freed using free();
```c
char* json;
size_t jsonlen;
if (HalonMTA_config_to_json(cfg, &json, &jsonlen))
printf("%s\n", json);
free(json);
```
### int HalonMTA_hsl_value_type(const HalonHSLValue \*value)
Return the type of the HSL value, the following defines may be returned. For types that cannot be used in HSL plugins
*HALONMTA_HSL_TYPE_UNSUPPORTED* is returned (eg. HSL function or resources).
> * *HALONMTA_HSL_TYPE_NONE*
> * *HALONMTA_HSL_TYPE_BOOLEAN*
> * *HALONMTA_HSL_TYPE_NUMBER*
> * *HALONMTA_HSL_TYPE_STRING*
> * *HALONMTA_HSL_TYPE_ARRAY*
> * *HALONMTA_HSL_TYPE_FFI_POINTER*
> * *HALONMTA_HSL_TYPE_FILE*
> * *HALONMTA_HSL_TYPE_FUNCTION*
> * *HALONMTA_HSL_TYPE_MAILMESSAGE*
> * *HALONMTA_HSL_TYPE_X509*
> * *HALONMTA_HSL_TYPE_PRIVATEKEY*
> * *HALONMTA_HSL_TYPE_UNSUPPORTED*
### bool HalonMTA_hsl_value_get(const HalonHSLValue \*value, int type, void \*outval, size_t \*outlen)
Return the value of a HSL value.
The type argument must match the actual type of value.
All memory is owned by the `value` itself and must not be freed, or used after the plugin execution is done.
On error false is returned.
| Type | outval | outlen |
|-------------------------------|-------------|--------------------|
| HALONMTA_HSL_TYPE_BOOLEAN | &bool | n/a |
| HALONMTA_HSL_TYPE_NUMBER | &double | n/a |
| HALONMTA_HSL_TYPE_STRING | &char\* | &size_t (optional) |
| HALONMTA_HSL_TYPE_FFI_POINTER | &void\* | n/a |
| HALONMTA_HSL_TYPE_FILE | &FILE\* | n/a |
| HALONMTA_HSL_TYPE_X509 | &X509\* | n/a |
| HALONMTA_HSL_TYPE_PRIVATEKEY | &EVP_PKEY\* | n/a |
```c
char* string;
size_t stringl;
HalonMTA_hsl_value_get(value, HALONMTA_HSL_TYPE_STRING, &string, &stringl);
```
> **Note**
>
> When using a *FILE\** from *HALONMTA_HSL_TYPE_FILE*, always call *fseek* to the intended location before use.
### HalonHSLValue \*HalonMTA_hsl_object_property_get(HalonHSLValue \*object, const char \*name)
Get a reference to a property of an object by name. This can be used do retrieve function object etc.
If the properties doesn’t exist NULL is returned.
### HalonHSLValue \*HalonMTA_hsl_value_array_get(const HalonHSLValue \*value, size_t index, HalonHSLValue \*\*key)
Get a reference to the value and key item of an array based on the index (starts at zero).
If index doesn’t exist return NULL is returned and the key is not affected.
In order to lookup a specific key based on a value they array has to be iterated.
```c
size_t index = 0;
HalonHSLValue *k, *v;
while ((v = HalonMTA_hsl_value_array_get(value, index, &k)))
{
printf("%zu: type of k %d => v %d\n", index, HalonMTA_hsl_value_type(k), HalonMTA_hsl_value_type(v));
++index;
}
```
### size_t HalonMTA_hsl_value_array_length(const HalonHSLValue \*args)
Get the length of the HalonHSLValue array.
### HalonHSLValue \*HalonMTA_hsl_value_array_find(const HalonHSLValue \*value, const char \*key)
Get a reference to the value of the key.
If key doesn’t exist return NULL is returned.
```c
HalonHSLValue *v = HalonMTA_hsl_value_array_find(value, "mykey");
```
### bool HalonMTA_hsl_value_set(HalonHSLValue \*value, int type, const void \*inval, size_t inlen)
Set a value of value.
For strings the value is copied to HSL.
As for the FFI pointer type a new FFIPointer object is created which doesn’t own the memory (if ownership is not changed using the HSL FFI implementation).
On error false is returned.
| Type | inval | inlen | Comment |
|-------------------------------|-------------------|-------------------|---------------------------------------------------------------------------------------------|
| HALONMTA_HSL_TYPE_NONE | n/a | n/a | |
| HALONMTA_HSL_TYPE_BOOLEAN | &bool | n/a | |
| HALONMTA_HSL_TYPE_NUMBER | &double | n/a | |
| HALONMTA_HSL_TYPE_STRING | char\* | size_t (optional) | |
| HALONMTA_HSL_TYPE_ARRAY | n/a | n/a | |
| HALONMTA_HSL_TYPE_FFI_POINTER | void\* | n/a | |
| HALONMTA_HSL_TYPE_EXCEPTION | char\* | size_t (optional) | |
| HALONMTA_HSL_TYPE_X509 | X509\* | n/a | OpenSSL’s X509_free is called on destruction, but not X509_up_ref on initialization |
| HALONMTA_HSL_TYPE_PRIVATEKEY | EVP_PKEY\* | n/a | OpenSSL’s EVP_PKEY_free is called on destruction, but not EVP_PKEY_up_ref on initialization |
| HALONMTA_HSL_TYPE_THIS | HalonHSLContext\* | n/a | For HSL objects, return a reference to $this |
| HALONMTA_HSL_TYPE_COPY | HalonHSLValue\* | n/a | Copy a HSL value |
| HALONMTA_HSL_TYPE_FILE | char\* | size_t (optional) | |
| HALONMTA_HSL_TYPE_MAILMESSAGE | char\* | size_t (optional) | |
```c
HalonMTA_hsl_value_set(value, HALONMTA_HSL_TYPE_STRING, "Hello World", 0);
```
### bool HalonMTA_hsl_value_array_add(HalonHSLValue \*array, HalonHSLValue \*\*key, HalonHSLValue \*\*value)
Add a new key and value to an array. If the value isn’t None or an array, then return false. The values are initialized as None and should be set.
```c
HalonHSLValue *k, *v;
HalonMTA_hsl_value_array_add(ret, &k, &v);
HalonMTA_hsl_value_set(k, HALONMTA_HSL_TYPE_STRING, "key", 0);
HalonMTA_hsl_value_set(v, HALONMTA_HSL_TYPE_STRING, "value", 0);
```
> **Note**
>
> Consecutive calls to [`HalonMTA_hsl_value_array_add()`](#plugins_native:c.HalonMTA_hsl_value_array_add) will invalidate previous return *HalonHSLValue* pointers, use [`HalonMTA_config_array_get()`](#plugins_native:c.HalonMTA_config_array_get) by index to receive them again.
### bool HalonMTA_hsl_value_to_json2(const HalonHSLValue \*value, char \*\*out, size_t \*outlen, int flags)
Convert a HalonHSLValue to a JSON string. If the conversion fails, false is returned and out contains the error message (if any; otherwise null).
The memory returned in out must be freed using free().
There are two flags `HALONMTA_JSON_ENCODE_DEFAULT` and `HALONMTA_JSON_ENCODE_NO_ENSURE_ASCII`. See the HSL json_encode() function for documentation.
```c
char* json = nullptr;
size_t jsonlen;
if (HalonMTA_hsl_value_to_json(HalonMTA_hsl_argument_get(args, 0), &json, &jsonlen))
printf("%s\n", json);
free(json);
```
### bool HalonMTA_hsl_value_to_json(const HalonHSLValue \*value, char \*\*out, size_t \*outlen)
Same as [`HalonMTA_hsl_value_to_json2()`](#plugins_native:c.HalonMTA_hsl_value_to_json2) but with the `flags` set to `HALONMTA_JSON_ENCODE_DEFAULT`.
### bool HalonMTA_hsl_value_from_json2(HalonHSLValue \*value, const char \*json, char \*\*out, size_t \*outlen, int flags)
Convert a JSON string to a HalonHSLValue. If the conversion fails, false is returned and out contains the error message (if any; otherwise null).
The memory returned in out must be freed using free().
There are two flags `HALONMTA_JSON_DECODE_DEFAULT` and `HALONMTA_JSON_DECODE_UNICODE_BYTE`. See the HSL json_decode() function for documentation.
```c
char* err = nullptr;
size_t errlen;
if (!HalonMTA_hsl_value_from_json(ret, json, &err, &errlen))
{
if (err)
printf("Error: %s\n", json);
free(err);
}
```
### bool HalonMTA_hsl_value_from_json(HalonHSLValue \*value, const char \*json, char \*\*out, size_t \*outlen)
Same as [`HalonMTA_hsl_value_from_json2()`](#plugins_native:c.HalonMTA_hsl_value_from_json2) but with the `flags` set to `HALONMTA_JSON_ENCODE_DEFAULT`.
### Objects
The C API allows you to create HSL object. An object is a collection of functions and a custom pointer.
#### HalonHSLObject \*HalonMTA_hsl_object_new()
Return a pointer to a HSL object. This object must be freed using [`HalonMTA_hsl_object_delete()`](#plugins_native:c.HalonMTA_hsl_object_delete)
after copied to one or more HSL values using [`HalonMTA_hsl_value_set()`](#plugins_native:c.HalonMTA_hsl_value_set).
```c
HalonHSLObject* hho = HalonMTA_hsl_object_new();
HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_OBJECT, hho, 0);
HalonMTA_hsl_object_delete(hho);
```
#### bool HalonMTA_hsl_object_type_set(HalonHSLObject \*hho, const char \*type)
Set the class name. Used to identify the object.
```c
HalonHSLObject* hho = HalonMTA_hsl_object_new();
HalonMTA_hsl_object_type_set(hho, "MyObject");
HalonMTA_hsl_object_delete(hho);
```
#### void HalonMTA_hsl_object_ptr_set(HalonHSLObject \*hho, void \*ptr, void (\*free)(void\*));
Set the internal ptr of the object. The ptr may be changed when reusing the same object with [`HalonMTA_hsl_value_set()`](#plugins_native:c.HalonMTA_hsl_value_set).
The free function can be used to free the ptr when the HSL object is destroyed.
```c
HalonHSLObject* hho = HalonMTA_hsl_object_new();
HalonMTA_hsl_object_type_set(hho, "MyObject");
HalonMTA_hsl_object_ptr_set(hho, strdup("Hello World"), free);
HalonMTA_hsl_value_set(value1, HALONMTA_HSL_TYPE_OBJECT, hho, 0);
HalonMTA_hsl_object_ptr_set(hho, strdup("Hello World"), free);
HalonMTA_hsl_value_set(value2, HALONMTA_HSL_TYPE_OBJECT, hho, 0);
HalonMTA_hsl_object_delete(hho);
```
```go
// example on how to pass a function pointer in go
C.HalonMTA_hsl_object_ptr_set(hho, unsafe.Pointer(uintptr(0xcafe)), (C.free_fn)(unsafe.Pointer(C.dlsym(nil, C.CString("MyObject_free")))))
```
#### void HalonMTA_hsl_object_register_function(HalonHSLObject \*hho, const char \*name, HalonHSLFunction \*hhf);
Register member function on the object, this hhf function should be a regular HSL function [`myfunc()`](#plugins_native:c.myfunc).
When the hhf function is called [`HalonMTA_hsl_object_ptr_get()`](#plugins_native:c.HalonMTA_hsl_object_ptr_get) may be used to receive the pointer set by
[`HalonMTA_hsl_object_ptr_set()`](#plugins_native:c.HalonMTA_hsl_object_ptr_set) hence making it an instance function of the object.
```go
// example on how to pass a function pointer in go
C.HalonMTA_hsl_object_register_function(hho, C.CString("sayhi"), (*C.HalonHSLFunction)(unsafe.Pointer(C.dlsym(nil, C.CString("MyObject_sayhi")))))
```
#### void \*HalonMTA_hsl_object_ptr_get(HalonHSLContext \*hhc)
Get the instance pointer set by [`HalonMTA_hsl_object_ptr_set()`](#plugins_native:c.HalonMTA_hsl_object_ptr_set) from within a HSL function ([`myfunc()`](#plugins_native:c.myfunc)).
If not called within a object a null pointer is returned.
```c
static void sayhi(HalonHSLContext* hhc, HalonHSLArguments* args, HalonHSLValue* ret)
{
char* ptr = (char*)HalonMTA_hsl_object_ptr_get(hhc);
printf("%s\n", ptr);
HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_THIS, hhc, 0);
}
// ...
HalonHSLObject* hho = HalonMTA_hsl_object_new();
HalonMTA_hsl_object_type_set(hho, "MyObject");
HalonMTA_hsl_object_ptr_set(hho, strdup("Hello World"), free);
HalonMTA_hsl_object_register_function(hho, "sayhi", &sayhi);
HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_OBJECT, hho, 0);
HalonMTA_hsl_object_delete(hho);
```
#### void HalonMTA_hsl_object_delete(HalonHSLObject \*hho)
Free the HSL object created by [`HalonMTA_hsl_object_new()`](#plugins_native:c.HalonMTA_hsl_object_new).
### Callable
The C API allows you to call HSL function by function pointers (often referred to as callback functions).
#### HalonHSLFunctionCallable \*HalonMTA_hsl_function_callable_new(const HalonHSLContext \*hhc, const HalonHSLValue \*func)
Return a pointer to a HalonHSLFunctionCallable object. This object must be freed using [`HalonMTA_hsl_function_callable_delete()`](#plugins_native:c.HalonMTA_hsl_function_callable_delete).
If the *func* argument is not a callable function pointer this function returns null.
```c
HalonHSLFunctionCallable* hhfc = HalonMTA_hsl_function_callable_new(hhc, func);
HalonMTA_hsl_function_callable_delete(hhfc);
```
#### HalonHSLValue \*HalonMTA_hsl_function_callable_argument_add(HalonHSLFunctionCallable \*hhfc)
Add an argument to be used when invoked using [`HalonMTA_hsl_function_callable_invoke()`](#plugins_native:c.HalonMTA_hsl_function_callable_invoke).
The default value of the argument is *none*.
Previously return HalonHSLValue pointers are invalided when the next argument is requested.
```c
auto hhfc = HalonMTA_hsl_function_callable_new(hhc, func);
HalonMTA_hsl_value_set(
HalonMTA_hsl_function_callable_argument_add(hhfc),
HALONMTA_HSL_TYPE_STRING,
"Hello", 0);
HalonMTA_hsl_value_set(
HalonMTA_hsl_function_callable_argument_add(hhfc),
HALONMTA_HSL_TYPE_STRING,
"World", 0);
HalonMTA_hsl_function_callable_delete(hhfc);
```
#### bool HalonMTA_hsl_function_callable_invoke(const HalonHSLFunctionCallable \*hhfc, HalonHSLValue \*\*ret)
Invoke the HSL function. This function returns a boolean value where
* true: *ret* is the return value of the function
* false: *ret* is the exception thrown by the function
The lifetime of the *ret* pointer is bound to the HalonHSLFunctionCallable and is overwritten by each repeated invocation of this function.
```c
auto ctx = HalonMTA_hsl_function_callable_new(hhc, func);
HalonMTA_hsl_value_set(
HalonMTA_hsl_function_callable_argument_add(ctx),
HALONMTA_HSL_TYPE_STRING,
"Hello", 0);
HalonHSLValue* retval;
if (HalonMTA_hsl_function_callable_invoke(ctx, &retval))
HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_COPY, retval, 0);
else
HalonMTA_hsl_value_set(HalonMTA_hsl_throw(hhc), HALONMTA_HSL_TYPE_COPY, retval, 0);
HalonMTA_hsl_function_callable_delete(ctx);
```
A sample function pointer in HSL.
```php
myfunc(function($phrase) {
echo "$phrase World!";
});
```
#### void HalonMTA_hsl_function_callable_delete(HalonHSLFunctionCallable \*hho)
Free the HalonHSLFunctionCallable object created by [`HalonMTA_hsl_function_callable_new()`](#plugins_native:c.HalonMTA_hsl_function_callable_new).
### HSL plugin
#### HalonHSLValue \*HalonMTA_hsl_argument_get(const HalonHSLArguments \*args, size_t index)
Get the function call argument based on index (starting at zero). If the index doesn’t exists then NULL is returned.
```c
size_t index = 0;
HalonHSLValue *a;
while ((a = HalonMTA_hsl_argument_get(args, index)))
{
printf("%zu: type of a %d\n", index, HalonMTA_hsl_value_type(a));
++index;
}
```
#### size_t HalonMTA_hsl_argument_length(const HalonHSLArguments \*args)
Get the length of the HalonHSLArguments list.
#### HalonHSLValue \*HalonMTA_hsl_throw(HalonHSLContext \*hhc)
Get a pointer to the exception value that will be thrown when returning from the function. Any HSL type may be thrown. This supersedes any change to the return value.
```c++
static void example(HalonHSLContext* hhc, HalonHSLArguments* args, HalonHSLValue* ret)
{
HalonHSLValue* e = HalonMTA_hsl_throw(hhc);
HalonMTA_hsl_value_set(e, HALONMTA_HSL_TYPE_EXCEPTION, "An error has occurred", 0); // throw an instance of Exception
return;
}
```
#### void HalonMTA_hsl_suspend(HalonHSLContext \*hhc)
This function suspends the current execution of the script (and it doesn’t return until scheduled), allowing other scripts to be executed on the specific thread in a cooperative fashion (M:N threading).
```c++
static void example(HalonHSLContext* hhc, HalonHSLArguments* args, HalonHSLValue* ret)
{
std::thread t([hhc] {
sleep(5);
HalonMTA_hsl_schedule(hhc);
});
HalonMTA_hsl_suspend(hhc);
// continue here after 5 seconds
t.join();
}
```
#### void HalonMTA_hsl_suspend_return(HalonHSLContext \*hhc)
This function suspends the current execution of the script after the plugin function has returned to the MTA from the plugin (and it doesn’t return until scheduled), allowing other scripts to be executed on the specific thread in a cooperative fashion (M:N threading).
This function should be used when not possible to suspend the execution inside the plugin itself. This is the eg. case when building plugins in GO (cgo).
```go
func sleepTask(hhc *C.HalonHSLContext)
{
time.Sleep(5 * time.Seconds);
C.HalonMTA_hsl_schedule(hhc);
}
//export sleep
func sleep(hhc *C.HalonHSLContext, args *C.HalonHSLArguments, ret *C.HalonHSLValue)
{
go sleepTask(hhc);
C.HalonMTA_hsl_suspend_return(hhc);
}
```
#### void HalonMTA_hsl_schedule(HalonHSLContext \*hhc)
Reschedule the execution of a suspended execution. The script execution will be requeued onto the assigned threadpool queue. This function is thread-safe and may be called at any time (before or after the [`HalonMTA_hsl_suspend()`](#plugins_native:c.HalonMTA_hsl_suspend)).
#### bool HalonMTA_hsl_module_register_function(HalonHSLRegisterContext \*hhrc, const char \*name, HalonHSLFunction \*func)
Register a function to the plugins module namespace. Must only be called in [`Halon_hsl_register()`](#plugins_native:c.Halon_hsl_register).
If func is null the function is obtained by the MTA using dlsym (this is useful when writing plugins in eg. GO (cgo) which doesn’t easily support C function pointers).
If func is null, in order to prevent naming conflicts, function could be defined and referenced with a custom namespace eg. “xyz_\_myfunc”, while the registered function name would only be “myfunc”.
On error false is returned.
```c
HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
HalonMTA_hsl_module_register_function(hhrc, "myfunc", myfunc);
return true;
}
```
For GO (cgo) both options are available. The later also needs to include `#include `.
```go
//export myfunc
func myfunc(hhc *C.HalonHSLContext, args *C.HalonHSLArguments, ret *C.HalonHSLValue) {
}
//export Halon_hsl_register
func Halon_hsl_register(hhrc *C.HalonHSLRegisterContext) C.bool {
C.HalonMTA_hsl_module_register_function(hhrc, C.CString("myfunc"), nil);
C.HalonMTA_hsl_module_register_function(hhrc, C.CString("myfunc"), (*C.HalonHSLFunction)(unsafe.Pointer(C.dlsym(nil, C.CString("myfunc")))));
}
```
#### bool HalonMTA_hsl_module_register_static_function(HalonHSLRegisterContext \*hhrc, const char \*classname, const char \*name, HalonHSLFunction \*func)
Register a static class function in the plugins module namespace. Must only be called in [`Halon_hsl_register()`](#plugins_native:c.Halon_hsl_register).
If must be called after [`HalonMTA_hsl_module_register_function()`](#plugins_native:c.HalonMTA_hsl_module_register_function) so that the class name (function namespace) is created.
If func is null the function is obtained by the MTA using dlsym (this is useful when writing plugins in eg. GO (cgo) which doesn’t easily support C function pointers).
If func is null, in order to prevent naming conflicts, function could be defined and referenced with a custom namespace eg. “xyz_\_myfunc”, while the registered function name would only be “myfunc”.
On error false is returned.
```c
HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
HalonMTA_hsl_module_register_function(hhrc, "MyClass", MyClass);
HalonMTA_hsl_module_register_static_function(hhrc, "MyClass", "staticfunction", MyClass_staticfunction);
return true;
}
```
#### bool HalonMTA_hsl_register_function(HalonHSLRegisterContext \*hhrc, const char \*name, HalonHSLFunction \*func)
Register a function to HSL. Must only be called in [`Halon_hsl_register()`](#plugins_native:c.Halon_hsl_register).
If func is null the function is obtained by the MTA using dlsym (this is useful when writing plugins in eg. GO (cgo) which doesn’t easily support C function pointers).
If func is null, in order to prevent naming conflicts, function could be defined and referenced with a custom namespace eg. “xyz_\_myfunc”, while the registered function name would only be “myfunc”.
On error false is returned.
```c
HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
HalonMTA_hsl_register_function(hhrc, "myfunc", myfunc);
return true;
}
```
```go
//export Halon_hsl_register
func Halon_hsl_register(hhrc *C.HalonHSLRegisterContext) C.bool {
C.HalonMTA_hsl_register_function(hhrc, C.CString("myfunc"), nil);
}
```
#### bool HalonMTA_hsl_register_static_function(HalonHSLRegisterContext \*hhrc, const char \*classname, const char \*name, HalonHSLFunction \*func)
Register a static class function in HSL. Must only be called in [`Halon_hsl_register()`](#plugins_native:c.Halon_hsl_register).
If must be called after [`HalonMTA_hsl_register_function()`](#plugins_native:c.HalonMTA_hsl_register_function) so that the class name (function namespace) is created.
If func is null the function is obtained by the MTA using dlsym (this is useful when writing plugins in eg. GO (cgo) which doesn’t easily support C function pointers).
If func is null, in order to prevent naming conflicts, function could be defined and referenced with a custom namespace eg. “xyz_\_myfunc”, while the registered function name would only be “myfunc”.
On error false is returned.
```c
HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
HalonMTA_hsl_register_function(hhrc, "MyClass", MyClass);
HalonMTA_hsl_register_static_function(hhrc, "MyClass", "staticfunction", MyClass_staticfunction);
return true;
}
```
### Lists plugin
The list plugin may control the content of [lists](#config_smtpd:config-list) in the [queue policy](#config_policy), [queue suspend](#config_suspend)
and [queue delivery](#config_delivery) configurations.
#### bool HalonMTA_queue_list_add(int type, const char \*name);
Add a list, this is usually done in the [`Halon_early_init()`](#plugins_native:c.Halon_early_init) function.
The type should be one of the `HALONMTA_QUEUE_` defines.
The name should be given without the “@” prefix.
On error false is returned.
#### bool HalonMTA_queue_list_delete(int type, const char \*name);
Delete a list.
On error false is returned.
#### bool HalonMTA_queue_list_item_add(int type, const char \*name, const char \*item);
Add an item to a list.
In order to apply the change call [`HalonMTA_queue_list_reload()`](#plugins_native:c.HalonMTA_queue_list_reload).
On error false is returned.
#### char \*HalonMTA_queue_list_item_get(int type, const char \*name, size_t index, size_t \*length);
Get an item from a list based on the index (starts at zero).
The returned item must be freed by the caller.
On error NULL is returned.
#### bool HalonMTA_queue_list_item_delete(int type, const char \*name, const char \*item);
Delete an item from a list.
In order to apply the change call [`HalonMTA_queue_list_reload()`](#plugins_native:c.HalonMTA_queue_list_reload).
On error false is returned.
#### bool HalonMTA_queue_list_reload(int type, const char \*name);
Reload a list making the changes affect the queue policies.
On error false is returned.
### Inject plugin
The inject plugins API allows you to submit messages to the MTA (EOD hook) without using the SMTP or HTTP API/Protocol.
In order to import a plain *HQF* file you may use the [import plugin](#plugins_native:import-plugin) API.
#### HalonInjectContext \*HalonMTA_inject_new2(const char \*serverid, const char \*transactionid);
This function create a new *HalonInjectContext\** context to be used when submitting a message. If the function fails (eg. due to missing serverid, disk full etc) then NULL is returned.
The type of the serverid ([`servers[].type`](#config_startup:confval-servers-.type)) used must be *plugin*.
If the transactionid (uuid) argument is null, the system will generate the transactionid.
The pointer (context) must be passed and successfully used with [`HalonMTA_inject_commit()`](#plugins_native:c.HalonMTA_inject_commit) or [`HalonMTA_inject_abort()`](#plugins_native:c.HalonMTA_inject_abort) in order to prevent memory and resource leaks.
```c
HalonInjectContext *hic = HalonMTA_inject_new2("plugin1", nullptr);
...
if (!HalonMTA_inject_commit(hic))
HalonMTA_inject_abort(hic);
```
#### HalonInjectContext \*HalonMTA_inject_new(const char \*serverid);
Same as [`HalonMTA_inject_new2()`](#plugins_native:c.HalonMTA_inject_new2) but with the `transactionid` set to `NULL` .
#### void HalonMTA_inject_abort(HalonInjectContext \*hic);
This function should be called on all code-paths unless [`HalonMTA_inject_commit()`](#plugins_native:c.HalonMTA_inject_commit) returns true in order to prevent memory and resource leaks.
#### bool HalonMTA_inject_sender_set(HalonInjectContext \*hic, const char \*localpart, const char \*domain);
This function sets the sender localpart and domain of the message.
```c
HalonMTA_inject_sender_set(hic, "user", "example.com");
// or
HalonMTA_inject_sender_set(hic, nullptr, nullptr); // empty-sender
```
#### bool HalonMTA_inject_connection_set(HalonInjectContext \*hic, const char \*connectionid, const char \*remoteip, size_t remoteport, const char \*localip, size_t localport);
This function sets the connection information attached to the message.
#### bool HalonMTA_inject_recipient_add(HalonInjectContext \*hic, const char \*localpart, const char \*domain, const char \*transportid);
This function adds a recipient to the message. If transportid is NULL the servers default will be used.
```c
HalonMTA_inject_recipient_add(hic, "user", "example.org", nullptr);
```
#### bool HalonMTA_inject_message_append(HalonInjectContext \*hic, const char \*data, size_t length);
This function appends content to a message, it may be called multiple times. If length is 0, the length will be determined by the C library strlen() function.
```c
while (!feof(fp))
{
char buf[8192];
size_t r = fread(buf, sizeof buf, 1, fp);
if (r > 0)
HalonMTA_inject_message_append(hic, buf, r);
}
```
#### bool HalonMTA_inject_callback_set(HalonInjectContext \*hic, HalonInjectResultFunction \*cb, void \*userptr);
This function registers a callback function and a user pointer that will be called after the message has be submitted and processed by the EOD hook.
```c
MyInjectContext* mic = new MyInjectContext;
HalonMTA_inject_callback_set(hic, MyInjectCallback, mic);
```
When message has executed the EOD hook, the cb function will be called (in the thread of the EOD hook). Use the [`HalonMTA_inject_result_getinfo()`](#plugins_native:c.HalonMTA_inject_result_getinfo) function to get the result.
```c
static void MyInjectCallback(HalonInjectResultContext* hirc, void* userptr)
{
MyInjectContext* mic = (MyInjectContext*)userptr;
short int code;
HalonMTA_inject_result_getinfo(hirc, HALONMTA_RESULT_CODE, nullptr, 0, &code, nullptr);
}
```
#### bool HalonMTA_inject_getinfo(HalonInjectContext \*hic, int type, const void \*inval, size_t inlen, void \*outval, size_t \*outlen);
Get info about the *HalonInjectContext*. The following types are available. No memory is owned by the caller. For most types
inval and inlen should be (NULL and 0), but for tags it’s the number index of the tag and for metadata the string key index.
On error false is returned.
| Type | inval | inlen | outval | outlen |
|--------------------------------|---------|---------|----------------|----------|
| HALONMTA_MESSAGE_METADATA | n/a | n/a | &HalonHSLValue | n/a |
| HALONMTA_MESSAGE_TRANSACTIONID | n/a | n/a | &char\* | &size_t |
#### bool HalonMTA_inject_commit(HalonInjectContext \*hic);
This function submits messages to the MTA. If this function return false [`HalonMTA_inject_abort()`](#plugins_native:c.HalonMTA_inject_abort) must be called to prevent memory and resource leaks.
```c
HalonInjectContext *hic = HalonMTA_inject_new("plugin1");
...
if (!HalonMTA_inject_commit(hic))
HalonMTA_inject_abort(hic);
```
#### bool HalonMTA_inject_result_getinfo(HalonInjectResultContext \*hirc, int type, const void \*inval, size_t inlen, void \*outval, size_t \*outlen);
This function is used with a *HalonInjectResultContext* pointer obtained in the callback set by [`HalonMTA_inject_callback_set()`](#plugins_native:c.HalonMTA_inject_callback_set). The following types are available. No memory is owned by the caller. For most types
inval and inlen should be (NULL and 0), but for tags it’s the number index of the tag and for metadata the string key index.
On error false is returned.
| Type | inval | inlen | outval | outlen |
|----------------------------------|---------|---------|----------------|----------|
| HALONMTA_RESULT_CODE | n/a | n/a | &short int | n/a |
| HALONMTA_RESULT_ENHANCED_CLASS | n/a | n/a | &short int | n/a |
| HALONMTA_RESULT_ENHANCED_SUBJECT | n/a | n/a | &short int | n/a |
| HALONMTA_RESULT_ENHANCED_DETAIL | n/a | n/a | &short int | n/a |
| HALONMTA_RESULT_REASON | n/a | n/a | &char\* | &size_t |
| HALONMTA_RESULT_METADATA | n/a | n/a | &HalonHSLValue | n/a |
```c
static void MyInjectCallback(HalonInjectResultContext* hirc, void* userptr)
{
HalonHSLValue *metadata;
HalonMTA_inject_result_getinfo(hirc, HALONMTA_RESULT_METADATA, nullptr, 0, &metadata, nullptr);
}
```
### Import plugin
The import plugins API allows you to import *HQF* data directly into the queue (pre-delivery hook).
In order to inject a plain *message/rfc822* you may use the [inject plugin](#plugins_native:inject-plugin) API.
#### HalonImportContext \*HalonMTA_import_new(int flags);
This function create a new *HalonImportContext\** context to be used when importing a message.
The pointer (context) must be passed and successfully used with [`HalonMTA_import_commit()`](#plugins_native:c.HalonMTA_import_commit) or [`HalonMTA_import_abort()`](#plugins_native:c.HalonMTA_import_abort) in order to prevent memory and resource leaks.
```c
HalonImportContext *hic = HalonMTA_import_new(0);
...
if (!HalonMTA_import_commit(hic))
HalonMTA_import_abort(hic);
```
| Flag | Purpose |
|-------------------------------------|-----------------------------------|
| HALONMTA_IMPORT_RESET_TRANSACTIONID | Generate a new transaction id |
| HALONMTA_IMPORT_RESET_TS | Set the timestamp to current time |
| HALONMTA_IMPORT_RESET_RETRYCOUNT | Reset the retry count to zero |
#### void HalonMTA_import_abort(HalonImportContext \*hic);
This function should be called on all code-paths unless [`HalonMTA_import_commit()`](#plugins_native:c.HalonMTA_import_commit) returns true in order to prevent memory and resource leaks.
#### bool HalonMTA_import_append(HalonImportContext \*hic, const char \*data, size_t length);
This function appends *HQF* data, it may be called multiple times. Since *HQF* files are partially in a binary format, *length* must always be specified.
```c
while (!feof(fp))
{
char buf[8192];
size_t r = fread(buf, sizeof buf, 1, fp);
if (r > 0)
HalonMTA_import_append(hic, buf, r);
}
```
#### bool HalonMTA_import_commit(HalonImportContext \*hic);
This function imports the *HQF* data to the MTA. If this function return false [`HalonMTA_import_abort()`](#plugins_native:c.HalonMTA_import_abort) must be called to prevent memory and resource leaks.
```c
HalonImportContext *hic = HalonMTA_import_new(0);
...
if (!HalonMTA_import_commit(hic))
HalonMTA_import_abort(hic);
```
### Queue plugin
#### bool HalonMTA_queue_getinfo(HalonQueueContext \*hqc, int type, const void \*inval, size_t inlen, void \*outval, size_t \*outlen)
Get info about a message in queue system. The following types are available. No memory is owned by the caller. For most types
inval and inlen should be (NULL and 0), but for tags it’s the number index of the tag and for metadata the string key index.
On error false is returned.
| Type | inval | inlen | outval | outlen |
|------------------------|---------|---------|----------------------|--------------------|
| HALONMTA_INFO_MESSAGE | NULL | 0 | &HalonQueueMessage\* | n/a |
| HALONMTA_INFO_RETURN | NULL | 0 | &HalonHSLValue\* | n/a |
| HALONMTA_INFO_LOCALIPS | NULL | 0 | &const char\*[] | &size_t (optional) |
#### bool HalonMTA_queue_setinfo(HalonQueueContext \*hqc, int type, const void \*inval, size_t inlen)
Set properites of about a message in queue system. The following types are available.
On error false is returned.
| Type | inval | inlen |
|------------------------|-----------------|--------------------|
| HALONMTA_INFO_LOCALIPS | &const char\*[] | &size_t (optional) |
#### bool HalonMTA_message_getinfo(HalonQueueMessage \*hqm, int type, const void \*inval, size_t inlen, void \*outval, size_t \*outlen)
Get info about a message. The following types are available. No memory is owned by the caller. For most types
inval and inlen should be (NULL and 0), but for tags it’s the number index of the tag and for metadata the string key index.
On error false is returned.
| Type | inval | inlen | outval | outlen |
|-------------------------------------|---------|-------------------|------------------|--------------------|
| HALONMTA_MESSAGE_TRANSPORTID | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_LOCALIP | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_REMOTEIP | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_REMOTEMX | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_RECIPIENTDOMAIN | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_JOBID | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_GROUPING | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_RECIPIENTLOCALPART | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_SENDERDOMAIN | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_SENDERLOCALPART | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_TRANSACTIONID | NULL | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_QUEUEID | NULL | 0 | &size_t | n/a |
| HALONMTA_MESSAGE_TAGS | &size_t | 0 | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_METADATA | char\* | size_t (optional) | &const char\* | &size_t (optional) |
| HALONMTA_MESSAGE_RETRYCOUNT | NULL | 0 | &size_t | n/a |
| HALONMTA_MESSAGE_CONTEXT | NULL | 0 | &HalonHSLValue\* | n/a |
#### char \*HalonMTA_queue_suspend_add5(const char \*id, const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, const char \*grouping, const char \*tenantid, const char \*tag, const char \*propv[], size_t propl, double ttl)
Add a suspend based on conditions. You may optionally pick the `id` (or `NULL`). If any of the conditions (`transportid`, `localip`, `remoteip`, `remotemx`, `recipientdomain`, `jobid`, `grouping` or `tenantid`) is `NULL` it will match any value. The `tag` and `ttl` are optional (and may be set to `NULL` or `0`). The `ttl` is in seconds.
The `propv` array should contain even number of items (as indicated by `propl`), the first string in pairs will be the property name, and the second the property value.
The returned id must be freed by the caller (regardless if it was specified or not). On error NULL is returned.
#### char \*HalonMTA_queue_suspend_add4(const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, const char \*grouping, const char \*tenantid, const char \*tag, const char \*propv[], size_t propl, double ttl)
Same as [`HalonMTA_queue_suspend_add5()`](#plugins_native:c.HalonMTA_queue_suspend_add5) but with the `id` set to `NULL` .
#### char \*HalonMTA_queue_suspend_add3(const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, const char \*grouping, const char \*tenantid, const char \*tag, double ttl)
Same as [`HalonMTA_queue_suspend_add4()`](#plugins_native:c.HalonMTA_queue_suspend_add4) but with the `propv` set to `NULL` and `propl` set to `0`.
#### char \*HalonMTA_queue_suspend_add2(const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, const char \*grouping, const char \*tag, double ttl)
Same as [`HalonMTA_queue_suspend_add3()`](#plugins_native:c.HalonMTA_queue_suspend_add3) but with the `tenantid` set to `NULL`.
#### char \*HalonMTA_queue_suspend_add(const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, const char \*tag, double ttl)
Same as [`HalonMTA_queue_suspend_add2()`](#plugins_native:c.HalonMTA_queue_suspend_add2) but with the `grouping` set to `NULL`.
#### bool HalonMTA_queue_suspend_update2(const char \*id, const char \*tag, const char \*propv[], size_t propl, double ttl)
Update a suspensions based the id. The `ttl` and `ttl` are optional (and may be set to `NULL` or `0`). The `ttl` is in seconds.
The `propv` array should contain even number of items (as indicated by `propl`), the first string in pairs will be the property name, and the second the property value.
On error false is returned.
#### bool HalonMTA_queue_suspend_update(const char \*id, const char \*tag, double ttl)
Same as [`HalonMTA_queue_suspend_update2()`](#plugins_native:c.HalonMTA_queue_suspend_update2) but with the `propv` set to `NULL` and `propl` set to `0` .
#### bool HalonMTA_queue_suspend_delete(const char \*id)
Delete a suspensions based the id. On error false is returned.
#### char \*HalonMTA_queue_policy_add6(const char \*id, int fields, int type, const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, const char \*grouping, const char \*tenantid, size_t concurrency, size_t messages, double interval, int ratealgorithm, double connectinterval, const char \*tag, const char \*propv[], size_t propl, bool stop, bool cluster, double ttl)
Add a queue policy for the specific fields (the defines should be binary OR’ed) and of a specific type. If any of the conditions (`transportid`, `localip`, `remoteip`, `remotemx`, `recipientdomain`, `jobid`, `grouping` or `tenantid`) is `NULL` it will match any value.
The `concurrency`, `messages`, `interval`, `connectinterval`, `tag`, `propv`, `propl` and `ttl` are optional and may be set to `NULL` or `0`. The `stop` indicates if the policy should trigger a stop of policy evaluation (short-circuit). The `interval` and `ttl` is in seconds.
The `propv` array should contain even number of items (as indicated by `propl`), the first string in pairs will be the property name, and the second the property value.
You may optionally pick the `id` (or `NULL`). The returned id must be freed by the caller (regardless if it was specified or not). On error NULL is returned.
| Fields | |
|--------------------------------|----|
| HALONMTA_QUEUE_TRANSPORTID | |
| HALONMTA_QUEUE_LOCALIP | |
| HALONMTA_QUEUE_REMOTEIP | |
| HALONMTA_QUEUE_REMOTEMX | |
| HALONMTA_QUEUE_RECIPIENTDOMAIN | |
| HALONMTA_QUEUE_JOBID | |
| HALONMTA_QUEUE_GROUPING | |
| HALONMTA_QUEUE_TENANTID | |
| Types | |
|------------------------------|----|
| HALONMTA_POLICY_TYPE_DYNAMIC | |
| HALONMTA_POLICY_TYPE_BACKOFF | |
| HALONMTA_POLICY_TYPE_WARMUP | |
#### char \*HalonMTA_queue_policy_add5(int fields, const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, const char \*grouping, const char \*tenantid, size_t concurrency, size_t messages, double interval, double connectinterval, const char \*tag, const char \*propv[], size_t propl, bool stop, bool cluster, double ttl)
Same as [`HalonMTA_queue_policy_add6()`](#plugins_native:c.HalonMTA_queue_policy_add6) but with the `id` set to `NULL`, `type` set to `HALONMTA_POLICY_TYPE_DYNAMIC` and `ratealgorithm` set to `HALONMTA_RATE_ALGORITHM_DEFAULT`.
#### char \*HalonMTA_queue_policy_add4(int fields, const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, const char \*grouping, const char \*tenantid, size_t concurrency, size_t messages, double interval, const char \*tag, const char \*propv[], size_t propl, bool stop, double ttl)
Same as [`HalonMTA_queue_policy_add5()`](#plugins_native:c.HalonMTA_queue_policy_add5) but with the `connectinterval` set to `0` and `cluster` set to `true`.
#### char \*HalonMTA_queue_policy_add3(int fields, const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, const char \*grouping, size_t concurrency, size_t messages, double interval, const char \*tag, const char \*propv[], size_t propl, bool stop, double ttl)
Same as [`HalonMTA_queue_policy_add4()`](#plugins_native:c.HalonMTA_queue_policy_add4) but with the `tenantid` set to `NULL`.
#### char \*HalonMTA_queue_policy_add2(int fields, const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, size_t concurrency, size_t messages, double interval, const char \*tag, const char \*propv[], size_t propl, bool stop, double ttl)
Same as [`HalonMTA_queue_policy_add3()`](#plugins_native:c.HalonMTA_queue_policy_add3) but with the `grouping` set to `NULL`.
#### char \*HalonMTA_queue_policy_add(int fields, const char \*transportid, const char \*localip, const char \*remoteip, const char \*remotemx, const char \*recipientdomain, const char \*jobid, size_t concurrency, size_t messages, double interval, const char \*tag, double ttl)
Same as [`HalonMTA_queue_policy_add2()`](#plugins_native:c.HalonMTA_queue_policy_add2) but with the `propv` set to `NULL`, `propl` set to `0` and `stop` argument set to `false`.
#### bool HalonMTA_queue_policy_update4(const char \*id, size_t concurrency, size_t messages, double interval, const char \*tag, const char \*propv[], size_t propl, bool stop, bool cluster, double ttl)
Update a queue policy based on id.
The `concurrency`, `messages`, `interval`, `tag` and `ttl` are optional and may be set to `NULL` or `0`. The `interval` and `ttl` is in seconds.
The `propv` array should contain even number of items (as indicated by `propl`), the first string in pairs will be the property name, and the second the property value.
On error false is returned.
#### bool HalonMTA_queue_policy_update3(const char \*id, size_t concurrency, size_t messages, double interval, const char \*tag, const char \*propv[], size_t propl, bool cluster, double ttl)
Same as [`HalonMTA_queue_policy_update4()`](#plugins_native:c.HalonMTA_queue_policy_update4) but with the `stop` set to `false`.
#### bool HalonMTA_queue_policy_update2(const char \*id, size_t concurrency, size_t messages, double interval, const char \*tag, const char \*propv[], size_t propl, double ttl)
Same as [`HalonMTA_queue_policy_update3()`](#plugins_native:c.HalonMTA_queue_policy_update3) but with the `cluster` set to `true`.
#### bool HalonMTA_queue_policy_update(const char \*id, size_t concurrency, size_t messages, double interval, const char \*tag, double ttl)
Same as [`HalonMTA_queue_policy_update2()`](#plugins_native:c.HalonMTA_queue_policy_update2) but with the `propv` set to `NULL` and `propl` set to `0`.
#### bool HalonMTA_queue_policy_delete(const char \*id)
Delete a queue policy based the id. On error false is returned.
#### bool HalonMTA_queue_policy_divide(size_t count)
Divide policies with concurrency, rate and connectinterval statically based on number of hosts in a cluster.
### Delivery plugin
#### bool HalonMTA_deliver_getinfo(HalonDeliverContext \*hdc, int type, const void \*inval, size_t inlen, void \*outval, size_t \*outlen)
Get info about the delivery. The following types are available. No memory is owned by the caller. For most types
inval and inlen should be (NULL and 0), but for tags it’s the number index of the tag and for metadata the string key index.
On error false is returned.
| Type | inval | inlen | outval | outlen |
|-------------------------|---------|---------|----------------------|----------|
| HALONMTA_INFO_MESSAGE | NULL | 0 | &HalonQueueMessage\* | n/a |
| HALONMTA_INFO_FILE | NULL | 0 | &FILE\* | n/a |
| HALONMTA_INFO_ARGUMENTS | NULL | 0 | &HalonHSLValue\* | n/a |
| HALONMTA_INFO_RETURN | NULL | 0 | &HalonHSLValue\* | n/a |
```c
FILE* fp;
HalonMTA_deliver_getinfo(hdc, HALONMTA_INFO_FILE, NULL, 0, (void*)&fp, NULL);
char* string;
size_t stringl;
HalonMTA_deliver_getinfo(hdc, HALONMTA_INFO_METADATA, "mykey", 0, &string, &stringl);
HalonHSLValue* value;
HalonMTA_deliver_getinfo(hdc, HALONMTA_INFO_RETURN, NULL, 0, &value, NULL);
HalonMTA_hsl_value_set(value, HALONMTA_HSL_TYPE_STRING, "plugin result data", 0);
```
#### bool HalonMTA_deliver_setinfo(HalonDeliverContext \*hdc, int type, const void \*inval, size_t inlen)
Set the result of the delivery status. When the first property of *HALONMTA_RESULT_* or *HALONMTA_ERROR_* is set. The other properties will have their default values. On error false is returned.
| Type | inval | inlen | default |
|----------------------------------|------------|-------------------|-----------|
| HALONMTA_RESULT_CODE | &short int | n/a | 400 |
| HALONMTA_RESULT_ENHANCED_CLASS | &short int | n/a | 0 |
| HALONMTA_RESULT_ENHANCED_SUBJECT | &short int | n/a | 0 |
| HALONMTA_RESULT_ENHANCED_DETAIL | &short int | n/a | 0 |
| HALONMTA_RESULT_REASON | char\* | size_t (optional) | “Error” |
| HALONMTA_ERROR_TEMPORARY | &bool | n/a | true |
| HALONMTA_ERROR_REASON | char\* | size_t (optional) | “Error” |
#### bool HalonMTA_deliver_trace(HalonDeliverContext \*hdc, int flags, const void \*inval, size_t inlen)
This function can be used to add trace information (extracted using `halonctl queue trace`). This function can be used in any thread.
Flags may be either `0` (*inval* logged with timestamp and line wraps grouped) or `HALONMTA_TRACE_BINARY` (output the *inval* binary - as-is).
If *inlen* is zero, the length is determined by the null termination of *inval*.
If no trace is active, this function returns *false*.
```c
HALON_EXPORT
void Halon_deliver(HalonDeliverContext* hdc)
{
HalonMTA_deliver_trace(hdc, 0, "Hello", 0);
HalonMTA_deliver_done(hdc);
}
```
#### void HalonMTA_deliver_done(HalonDeliverContext \*hdc)
This function should be called when the delivery is completed, and after all info is set (using [`HalonMTA_deliver_setinfo()`](#plugins_native:c.HalonMTA_deliver_setinfo)). This function can be used in any thread.
The default result if not changed is a temporary error.
```c
HALON_EXPORT
void Halon_deliver(HalonDeliverContext* hdc)
{
short int status = 250;
HalonMTA_deliver_setinfo(hdc, HALONMTA_RESULT_CODE, &status, 0);
HalonMTA_deliver_setinfo(hdc, HALONMTA_RESULT_REASON, "OK", 0);
HalonMTA_deliver_done(hdc);
}
```
### Monitor plugin
#### bool HalonMTA_health_get()
Return the current manual set health status.
#### void HalonMTA_health_set(bool unhealthy)
Set the health status as unhealthy (not healthy).
#### bool HalonMTA_metrics_add(const char \*name, int type, const char \*help)
Add a metric. The `name` and `type` arguments are required. The `type` can be one of `HALONMTA_METRIC_COUNTER` or `HALONMTA_METRIC_GAUGE`. The `help` argument may be `NULL`.
#### bool HalonMTA_metrics_histogram_add(const char \*name, const char \*help, const double bucketv[], size_t bucketl)
Add a histogram metric. The `name`, `bucketv` and `bucketl` arguments are required. The `help` argument may be `NULL`.
#### bool HalonMTA_metrics_delete(const char \*name, const char \*labelv[], size_t labell)
Delete a metric. The `name` argument is required. If no label is given the entire metric is removed.
#### bool HalonMTA_metrics_increment(const char \*name, const char \*labelv[], size_t labell, double value)
Increment a metric (gauge and counter). The `name` argument is required. If no label is given the metric without label any label is incremented.
#### bool HalonMTA_metrics_decrement(const char \*name, const char \*labelv[], size_t labell, double value)
Decrement a metric (gauge). The `name` argument is required. If no label is given the metric without label any label is decremented.
#### bool HalonMTA_metrics_set(const char \*name, const char \*labelv[], size_t labell, double value)
Set a metric (gauge, counter or histogram). The `name` argument is required. If no label is given the metric without label any label is set.
# HSL plugins
Plugins written in HSL should be installed inside a per-plugin folder inside of [`environment.pluginspath`](#config_startup:confval-environment.pluginspath)`/hsl/example`.
```c
/opt/halon/plugins/hsl/
/opt/halon/plugins/hsl/example
/opt/halon/plugins/hsl/example/main.hsl
/opt/halon/plugins/hsl/example/example.hsl
```
A suggestive structure is to have a `main.hsl` file which imports (hence export) what should be availabe to the user of the plugin.
```javascript
import { hello } from "./example.hsl";
```
```javascript
function hello()
{
return "world";
}
```
The user of the plugin would import it using
```javascript
import { hello } from "extras://example";
echo hello(); // "world"
```
If the plugin example.hsl need access to plugin config [`plugins[].config`](#config_startup:confval-plugins-.config).
It can access the configuration using the following imports.
It allows you to build app that consumes the same configuration as native plugins.
```javascript
import $smtpd from "plugin-config://smtpd.yaml";
import $smtpdapp from "plugin-config://smtpd-app.yaml";
```
# Development
The `hsh` program allows for a convenient building and testing
environment when developing functions for the Halon scripting language.
The `hsh` program will call the following plugin functions.
> * [`Halon_version()`](#plugins_native:c.Halon_version)
> * [`Halon_hsl_register()`](#plugins_native:c.Halon_hsl_register)
> * [`Halon_init()`](#plugins_native:c.Halon_init)
> * [`Halon_cleanup()`](#plugins_native:c.Halon_cleanup)
Other function such as delivery functions has to be built and tested
using external testing frameworks or by using `smtpd` in a testing environment.
We recommend to use any of the examples in our
[GitHub](https://github.com/halon-extras?language=c%2B%2B) repository as the boilerplate to get started quickly.
## hsh
The `hsh` program will load all plugins in the `--config` file specified, and the plugins *app* configuration from the plugins section in the `--appconf` file specified (optional).
### hsh.yaml
```yaml
version: "6.12"
plugins:
- id: myplugin
path: myplugin.so
config:
setting1: true
```
### hsh-app.yaml
```yaml
version: "6.12"
plugins:
- id: myplugin
config:
setting2: true
```
Run the `hsh` program with the `--plugin` flag for each plugin to load.
```console
hsh --config hsh.yaml --appconf hsh-app.yaml --plugin myplugin test.hsl
```
# Examples
Plugin examples written in HSL, C, C++, Rust and Go are available in the [GitHub](https://github.com/halon-extras?language=c%2B%2B) repository.
# Changelog
## 6.12
Released 2026-10-28
- **Imp** MTA improvements
- Added support for [`servers[].timeout.idle`](#config_smtpd:confval-servers-.timeout.idle) for HTTP servers
- Added support for [`monitor.timeout.idle`](#config_smtpd:confval-monitor.timeout.idle) for the monitoring interface
- Added support exposing the HSL cache statistics on the monitoring interface
- Added support in the Proxy hook to request the `Next` line without sending a response
- Added support for lookupdirs for cert and CRL ([`environment.controlsocket.tls.lookupdirs.paths[]`](#config_startup:confval-environment.controlsocket.tls.lookupdirs.paths))
- Added `expires` argument to Post-delivery hook to see how many messages there are left until the message expires
- Added `expired` and `expires` argument to Pre-delivery hook
- Added message bound immutable data (context) to the queued message object, `MailMessage.setContext` and `MIME.setContext`
- Added [`resolver.threads.ready`](#config_startup:confval-resolver.threads.ready) threads to process ready DNS resolvings
- Added `state` to more `error`’s in post-delivery hook
- **Imp** Script language improvements
- Added a `reverse` options to `MIMEPart.getHeader` function
- Added support for `maxmessagesize` setting to the Connect hook
- Added support for `tls` setting to the Connect hook
- Added support for `password` protected private key `ec/ed25519/rsa_privatekey`
- Added support for a `miss_function` callback for `cache []`s
- Added a deterministic seed to `array_shuffle`, `array_random` and `random_number`
- Added a `keys` options to `array_filter` and `array_find` function
- Added a `exclude_headers` options to `Bounce` function
- Added support for sub-second resolution in `Socket.settimeout`
- Added support objects decoding options in `json_decode`
- Added support objects encoding in `json_encode`
- `foreach` may now iterate objects public members
- For SMTP verfications, `tls_verify_name` may now include the connecting peer with `tls_verify_name_include_host`
- **Imp** Native (C ABI) plugin improvements
- `HalonMTA_inject_new2` to support selecting the transactionid during injection
- **Bug**
- Expired DKIM signature errors now include full tag list
- Minor formating issues with DSN (missing <> on References)
- Outbound XCLIENT now sends HELO as first command after negotiation
- Protobuf API: Exclude on tag was not respected for policies and suspend listing
- Protobuf API: Removed unused support for using exclude on local-part in queue list and queue groupby
- Protobuf API: suspended filter did not work with only one suspend that suspends everything
- DNS cached the results by the longest and not shortest of multiple RR type responses
- **Note** Important changes
- Verifies that privatekey matches X.509 certificates during startup (early error)
- **Pkg** Packaging
- Update curl to 8.23.0
- Update openssl to 3.6.5
- Update c-ares to 1.34.9
## 6.11.4
Released 2026-09-11
- **Bug** Fix deadlock that could occur when `halon-clusterd` was restarted
## 6.11.3
Released 2026-07-31
- **Bug** Fix deadlock that could occur when `halon-clusterd` was restarted
## 6.11.2
Released 2026-07-13
- **Bug** systemd services now depend on `network-online.target` (instead of `network.target`)
- **Bug** C-ABI define issue with `HALONMTA_METRIC_*`
- **Bug** Fix timeout with fractions of seconds for outbound connections
- **Pkg** Packaging
- Reverted c-ares to 1.34.6 (patch release introduced a bug, CVE risk is low)
## 6.11.1
Released 2026-07-07
- **Bug** `halonconfig --unpack` could fail to unpack some files (keeping them in smtpd.yaml)
- **Bug** Made OpenMetrics output more compliant with standard
- **Pkg** Packaging
- Update libevent to 2.1.13
- Update c-ares to 1.34.7
## 6.11
Released 2026-06-25
- **Imp** MTA improvements
- Added support for [`servers[].tls.groups`](#config_smtpd:confval-servers-.tls.groups) to configure TLS key exchange groups throughout
- Now [`cluster.startup.hosts`](#config_startup:confval-cluster.startup.hosts) works without [`cluster.address`](#config_startup:confval-cluster.address)
- Added support for client name verification on the API endpoint `environment.controlsocket.tls.clientnames`
- Added support for custom CAs on the API endpoint `environment.controlsocket.tls.cacerts`
- Added support for custom CRLs on the API endpoint `environment.controlsocket.tls.crls`
- Added [`queues.pickup.prereaddata`](#config_smtpd:confval-queues.pickup.prereaddata) to control spool in behaviour and [`queues.pickup.backpressure`](#config_smtpd:confval-queues.pickup.backpressure) to enforce backpressure while stressed
- **Imp** Script language improvements
- Added native support for `soa` records in `dns_query` function
- Added `Map.toArray` and `Set.toArray` functions
- Added `X509.chain` functions
- Added `cms_type` function
- Added `cms_recipients` function
- Added `pkcs11_load` function to fetch `PrivateKeys` from an HSM
- Added `is_x509_resource` and `is_x509crl_resource` functions
- Added `cipher`, `usekeyid` option to `cms_encrypt` function
- Added `noattr`, `binary`, `nosmimecap`, `usekeyid` and `digest` option to `cms_sign` functions
- Added `binary` option to `cms_verify` functions
- `cms_verify` now always include `certs` in results (not trusted).
- **Imp** Command line tool improvements
- Improved performance on `queue list` with `--json` output.
- Added a `--localip-exact` option to always match a specific set of IPs.
- Added a `--localip-exclude` option.
- **Note** Important changes
- HTTP, Plugin and protobuf now also checks the [`queues.maxmessages`](#config_smtpd:confval-queues.maxmessages) setting
- Optional TLS is not default for configuration version 6.11 and above
- **Pkg** Packaging
- Update OpenSSL to 3.6.3
- Update Iconv to 1.19
- Update curl to 8.21.0
- Added support for Ubuntu 26.04 and RHEL10 (amd64, arm64)
- **Note** Reduced syslog output from verifyDKIM function in HSL
- **Note** Metadata keys prefix with `_` are no longer reserved
- **Note** 5min TCP keep-alive enabled for [`cluster.address`](#config_startup:confval-cluster.address) peer
- **Note** Improved random distribution for localip in the queue for certain traffic patterns
## 6.10.7
Released 2026-06-23
- **Bug** Formating of IPv6 DNS servers in `halontop` broke table rendering
- **Bug** Issue with multiple subnets matching in Protobuf queue filter logic
- **Bug** Fix issue with split threadpool for pre and post-delivery with background tasks
## 6.10.6
Released 2026-04-28
- **Pkg** Update OpenSSL to 3.5.6
- **Bug** Fixed issue where `helo` and `remotegreet` was not always available in Post-delivery hook
- **Bug** Fixed wrong content type for attached original messages in async bounces
## 6.10.5
Released 2026-04-10
- **Bug** Fixed a slow-growing memory leak
- **Bug** Do not include hqf or eml data in audit logs on import
## 6.10.4
Released 2026-03-17
- **Bug** Fixed issue with `halonconfig` on CentOS 7
- **Bug** Fixed issue with symlinks and `rootpath`/`pluginspath`
- **Bug** Fixed issue with `int` and `float` casting when using env variables in YAML config
## 6.10.3
Released 2026-03-13
- **Bug** Fixing a version issue in the configuration files with `version: 6.10`
## 6.10.2
Released 2026-03-12
- **Bug** Fix rare deadlock when fetching the queue suspend list on a busy system
- **Pkg** Update curl to 8.19.0
## 6.10.1
Released 2026-03-05
- **Bug** Fix issue with `halonconfig` `dist` output when using custom `!Env` tags
## 6.10
Released 2026-02-23
- **Imp** MTA improvements
- Inbound processing performance improvements
- Queue policy lookup performance with multiple conditions
- TLS support for smtpd control socket ([`environment.controlsocket.tls`](#config_startup:confval-environment.controlsocket.tls))
- Automatic priority for DNS resolver queues
- Added [`cluster.tls.verifyname`](#config_startup:confval-cluster.tls.verifyname) setting
- Start with a fixed number of assumed hosts if `clusterd` service is unavailable ([`cluster.startup.hosts`](#config_startup:confval-cluster.startup.hosts))
- Added support for `!Env` (environment variable substitution) in configuration files
- Allow for a custom search path ([`environment.pluginspath`](#config_startup:confval-environment.pluginspath))
- Set the supplementary groups (from the user) when privdropping
- **Imp** Script language improvements
- Added a `MailMessage.renameHeader` function
- Added support for `X509CRL` (Certificate Revocation List)
- Added a `metrics_get` function
- `json_decode` function now supports `unicode_byte` encoding
- Added capability to extract peer certificate chain
- Access HELO hostname from post-delivery
- Added support for `"tls" => "trust", "tls_trust_anchor" = []`
- Added easier support for none-expiring cache item (only by size and config reload)
- Added `remoteptrresult` to connect hooks arguments (to understand why a no FCrDNS was set)
- Allow HSL plugins to access it’s plugin configuration
- **Imp** Native (C ABI) plugin improvements
- `HalonMTA_hsl_value_to_json2` to support encoding flags
- `HalonMTA_hsl_value_from_json2` to support decoding flags
- **Imp** Command line tool improvements
- Added command to view the DNS queue (`halonctl resolver queue`)
- Added a queue analyze command to view queue structure (`halonctl queue analyze`)
- Show table footer and if more data is available in output
- **Dep** Important changes
- Renamed configuration setting `cluster.pki` to `cluster.tls` (for 6.10 configuration versions)
- **Pkg** Packaging
- Update OpenSSL to 3.5.5
- Update curl to 8.18.0
- Update c-ares to 1.34.6
- **Bug** Fix issue with casing for the `signDKIM` `exclude_headers` option
- **Bug** Fix schema validation issue with event threads for HTTP servers
## 6.9.1
Released 2025-12-08
- **Bug** Active config counter could get out of sync in `process-stats` output
- **Bug** Fix issue when unpacking configuration with `halonconfig`
- **Bug** Fix issue with metrics histograms without explicit +inf buckets
- **Bug** Use correct metric types for OpenMetrics endpoint
- **Bug** Fix logging issue when unloading messages from queue
- **Pkg** Update c-ares to 1.34.6
## 6.9
Released 2025-10-13
- **Imp** MTA improvements
- Policy API now supports static policies and improved filtering
- Structured audit logs for all control socket requests
- Added statistics counter for requeues by plugin
- **Imp** Script language improvements
- Added `ec_privatekey` and `is_ec_privatekey` functions
- Added `cms_(encrypt,decrypt,sign,verify)` functions
- Added `MIMEPart.getTypeParameter()` function to MailMessage
- Added `server_concurrency(serverid, remoteip)` to hooks
- Added `remotegreet` to connection object in postdelivery hook
- **Pkg** Packaging
- Update OpenSSL to 3.5.4
- Update curl to 8.16.0
- **Bug** Bug fixes
- Improved DNS behavior for multi-stack DNS error
## 6.8
Released 2025-08-27
- **Imp** MTA improvements
- Added support for multiple certificates on the same listern (eg. ECDSA and RSA)
- A new and modern SPF implementation
- **Pkg** Packaging
- Updated OpenSSL to 3.5.2
- Dropped 3rd party dependency on libfort (for CLI tables)
- Dropped 3rd party dependency on libspf2
- **Bug** Bug fixes
- The `mx_include` and `mx_exclude` resolver settings in HSL now match caseless
- Using negative numbers for custom properties in `smtpd-policy.yaml` is now allowed
- It was not possible to navigate to the system page in `halontop` using they arrow keys
- Fixed concurrency issue with clusterd and deliver plugins (eg. http-deliver)
- Improve error message for specific socket error
## 6.7.1
Released 2025-06-30
- **Bug** Fix configuration validation issue with `metrics_` functions
- **Pkg** Update curl to 8.14.1
## 6.7
Released 2025-06-25
- **Imp** MTA improvements
- Added regex matching for tags, when querying the list of current suspensions via [API](#api_reference)
- Added support for [`custom metrics`](#config_smtpd:confval-monitor.metrics) in [OpenMetrics](#openmetrics) output, see also [Monitor](#api_monitor) HTTP endpoint
- Added support for different types of [dynamic policies](#queue_policy:queue-policy-precedence) (backoff and warmup) with precedence
- Break dynamic, backoff and warmup policies into distinct lists (performance enhancement)
- Can set custom `algorithm` per policy in [`policies[].conditions[].then.rate`](#config_policy:confval-policies-.conditions-.then.rate) (also via protobuf API and HSL). This is used by the new warm-up process
- Disable Halon-generated DSNs on specific [`transports`](#config_smtpd:confval-transportgroups-.transports), useful when DSNs are enabled at [`transportgroups[].dsn`](#config_smtpd:confval-transportgroups-.dsn) level
- Can force sending a success notification (useful to work around misbehaving receiving systems, i.e. regardless of the DSN extension support declared by the recipient side)
- [halonctl process-stats](#cli_stats:halonctl-process-stats) now shows number of queries using TCP statistics (also in [halontop](#cli_stats:halontop))
- DNS resolver: treat certain recipient domains as higher-priority ([`resolver.domain.priority[]`](#config_smtpd:confval-resolver.domain.priority))
- DNS resolver: added support for priority queue deduplication
- DNS resolver: set custom [`resolver.ednsbuffersize`](#config_startup:confval-resolver.ednsbuffersize)
- DNS resolver: Can now set equal (ipv4/ipv6) [`resolver.preference`](#config_smtpd:confval-resolver.preference)
- Can configure protocols, ciphers and ciphersuites in `smtpd-app.yaml` (per transport/transportgroups, in [`transportgroups[].session.tls.protocols`](#config_smtpd:confval-transportgroups-.session.tls.protocols) etc) and in `smtpd-delivery.yaml` ([`delivery.conditions[].then.tls.protocols`](#config_delivery:confval-delivery.conditions-.then.tls.protocols) etc)
- Added support for domain wild-card prefix in grouping fields, e.g. [`queues.grouping.groupings[].recipientdomain[]`](#config_smtpd:confval-queues.grouping.groupings-.recipientdomain)
- Can set [`servers[].phases.rcptto.maxdomains`](#config_smtpd:confval-servers-.phases.rcptto.maxdomains) to restrict the number of unique recipient domains allowed per incoming message
- Enforce stricter multipart boundary parsing by default ([`servers[].phases.data.multipart.strict`](#config_smtpd:confval-servers-.phases.data.multipart.strict))
- **Imp** Native (C ABI) plugin improvements
- Added `Halon_queue_insert_callback` to alter information before queueing.
- Added `HalonMTA_queue_setinfo` to modify a message’s queued IPs.
- Added `HalonMTA_inject_connection_set` to set connection information for a injected message.
- Added ability to set a custom ID for `HalonMTA_queue_suspend_add5` and `HalonMTA_queue_policy_add6`
- Added ability to set stop flag when updating policies `HalonMTA_queue_policy_update4`
- **Imp** Command line tool improvements
- Ability to set equal (v4/v6) delivery preference for the `resolver query` command
- Ability to set custom ratealgorithm per policy
- Added regex matching for tags when doing suspend listing
- Added support for different types of dynamic policies (backoff and warmup)
- **Pkg** Packaging
- Migrated to pcre2 (from pcre1)
: - If you use the `halon-extras-suppression-list` or `halon-extras-bounce-list` packages these also needs to be updated to `2.0.0` and `2.0.1` respectively at the same time as the main `halon` package is
- Migrated to OpenSSL 3.5 (from OpenSSL 1.1.x). This will disable TLS 1.0 and 1.1 outbound by default
## 6.6.3
Released 2025-06-09
- **Bug** Forcefully disconnect pending `halonctl queue trace` on shutdown
- **Bug** Shorten timeout when connecting to clusterd
- **Bug** Fix issue with configuration validation for HTTPS metrics endpoints
- **Bug** Present proper errors when trying to reload appconf without permission
- **Bug** Fixed a rare issue with `cache []` that could case process restarts
- **Bug** Fixed a rare issue with metrics presenting a bad value for `script.suspended`
## 6.6.2
Released 2025-04-28
- **Bug** Fix rare issue with stale DNS queries
## 6.6.1
Released 2025-04-04
- **Bug** Fix issue with argument validation to `halonctl queue trace` command
- **Bug** Fix issue when configuring background update rate without specifing thread count
- **Bug** Fixed stability issue when running `halonctl process-stats` during a very short period during startup
- **Bug** Downgraded included c-ares package due to potential stability regression (1.33.1 same as in 6.4.x)
## 6.6
Released 2025-03-26
- **New** MTA features
- Added support for a [monitoring](#api_monitor) HTTP interface (for OpenMetrics and health checks)
- Added support for background queue updates (`halonctl queue update --background ...`)
- Added support for inbound connection prioritization [`servers[].priority.remoteips`](#config_smtpd:confval-servers-.priority.remoteips)
- Configuration files now accepts time and byte format as text, eg. `10MiB` or `2d` in more places
- Improved performance for configuration reload, and [metrics](#openmetrics) for its usage
- Added support for configuring *sourceip* when connecting to PROXY server(s) in configuration file
- **Imp** Script language improvements
- Added `uuidv5()` function
- Added `base64url_encode()` and `base64url_decode()` functions
- `queue_suspend()` and `queue_policy()` now supports user-defined UUIDs
- Added support for inbound connection prioritization in all hooks
- Added capability of sending copies of bounces in pre- and post-delivery.
- **Imp** Native (C ABI) plugin improvements
- Added [`HalonMTA_health_get()`](#plugins_native:c.HalonMTA_health_get) and [`HalonMTA_health_set()`](#plugins_native:c.HalonMTA_health_set) functions, to alter health status
- **Imp** Command line tool improvements
- Added DNS resolver insights in `halontop` and `halonctl resolver list`
- Added capability to run queue updates in background (`--background --progress`)
- Added capability to manually affect the system health (`halonctl health set --unhealthy`)
- Now support time and byte format as values, eg. `10MiB` or `2d` for a lot more commands.
- **Pkg** Packaging
- Update curl to 8.12.1
- Update libevent to 2.1.12
- **Bug** Bug fixes
- Fixed issues with JSON schema validation
- Fixed rare issue with `halonctl queue trace` command
- In HSL change the undefined behavior of doing math with *none* (behaves more like zero)
- In HSL fixed a rare issue with threading and background functions
- In HSL fixed a locking issue with `memory_update` function without initial value
## 6.5.5
Released 2025-01-19
- **Bug** Downgrade included curl package due to upstream regression with http-bulk/background plugin
## 6.5.4
Released 2025-01-17
- **Bug** Issue with *MailMessage.appendPart* keeping *MIME-Version* when root part content is wrapped
- **Bug** Fixed rare issue with *halonctl queue traces* error handling
## 6.5.3
Released 2025-01-10
- **Bug** Resolved issue with *dns_query()* that could result in an addition empty result when using `extended_result` option
## 6.5.2
Released 2024-12-22
- **Bug** Resolved issue with *smtp_lookup_rcpt()* function
## 6.5.1
Released 2024-12-19
- **Bug** Fix rare deadlock when using DANE together with connection pooling
## 6.5
Released 2024-12-16
- **New** MTA features
- Added clustered queue throttles (concurrency, rates & connect intervals)
- Added support for connect intervals (time in between connections)
- **Imp** MTA improvements
- Added support for compression on the HTTP submission API
- Support `?pretty=true` in HTTP JSON output
- Show TTL for rate suspends
- **Imp** Script language improvements
- Added `is_rsa_privatekey()` and `is_ed25519_privatekey()` functions
- **Imp** Native (C ABI) plugin improvements
- Added `HalonMTA_config_object_key_get` to get object key name by index
- Added `HalonMTA_queue_policy_divide`
- Added `HalonMTA_queue_policy_add5` and `HalonMTA_queue_policy_update3` functions, to support properties
- **Imp** Command line tool improvements
- Added missing filtering options for queue commands in halonctl
- Restructured halontop output
- **Bug** Bug fixes
- Allow more than 32 SAN in certificates in server certificates
## 6.4.1
Released 2024-08-28
- **Bug** Downgrade included curl package due to upstream regression with http-bulk plugin
## 6.4
Released 2024-08-26
- **Imp** MTA improvements
- Added capability to spool in from disk using a fixed rate (`spool.rate`)
- In `smtpd-app.yaml` allow [`transportgroups[].retry.during`](#config_smtpd:confval-transportgroups-.retry.during) and `count` to be configured without intervals
- Spool in mail in the order of [`spool.paths[].path`](#config_startup:confval-spool.paths-.path) in `smtpd-app.yaml`
- Added support for remoteip groupings ([`queues.grouping.groupings[].remoteip[]`](#config_smtpd:confval-queues.grouping.groupings-.remoteip))
- Added capability to configure [`servers[].commands.maxidle`](#config_smtpd:confval-servers-.commands.maxidle) and [`servers[].commands.maxbad`](#config_smtpd:confval-servers-.commands.maxbad)
- Inherit properties from policies to policy generated suspends
- Updated c-ares (asynchronous name resolver) to 1.33.1
- Updated curl (http client library) to 8.9.1
- **Imp** Script language improvements
- New [str_chunk()](https://docs.halon.io/hsl/archive/6.4-stable/functions.html#str_chunk) function
- New [array_random()](https://docs.halon.io/hsl/archive/6.4-stable/functions.html#array_random) function
- Support for AWS SIGv4 authentication in [http()](https://docs.halon.io/hsl/archive/6.4-stable/functions.html#http) function
- Added support for comments in CSV files (using `csv_decode` and `import`)
- Added support to add properties to suspends using [queue_suspend()](https://docs.halon.io/hsl/archive/6.4-stable/functions.html#queue_suspend) function
- Optimized usage of `continue` in loops
- **Imp** Native (C ABI) plugin improvements
- Added
[HalonMTA_queue_suspend_add4()](https://docs.halon.io/manual/archive/6.4-stable/plugins_native.html#c.HalonMTA_queue_suspend_add4),
[HalonMTA_queue_suspend_update2()](https://docs.halon.io/manual/archive/6.4-stable/plugins_native.html#c.HalonMTA_queue_suspend_update2)
[HalonMTA_queue_policy_update2()](https://docs.halon.io/manual/archive/6.4-stable/plugins_native.html#c.HalonMTA_queue_policy_update2)
functions, to support properties
- Added
[Halon_privileged_init()](https://docs.halon.io/manual/archive/6.4-stable/plugins_native.html#c.Halon_privileged_init)
function, supporting privileged initialization
- **Imp** Command line tool improvements
- Added capability to spool in using a fixed rate with `hqfmove`
- Added progress indicate to `hqfmove`
- **Bug** Various bug fixes
- Potential issue on manual message duplication
- **Dep** Important changes
- Do not increase process-stats counter `queue.pickup.misses` if the queue is empty
- Move C-ABI callback `Halon_cleanup` callback to be call last during shutdown
## 6.3.1
Released 2024-07-10
- **Bug** Couldn’t override [`transportgroups[].retry.during`](#config_smtpd:confval-transportgroups-.retry.during) with a longer expiration using pre-delivery `Try()` function
## 6.3
Released 2024-06-20
- **Imp** MTA improvements
- Support for the [PROXY protocol v2 TLV](https://github.com/halon-extras/proxytlv) by passing it to the [connect hook](https://docs.halon.io/hsl/archive/6.3-stable/connect.html#arguments)
- Added tenantid queue policy field for supporting segmenting for example sender domains more easily
- Make [`servers[].phases.data.maxheadersize`](#config_smtpd:confval-servers-.phases.data.maxheadersize) configurable
- Expired messages are now removed from the active queue as well
- Updated c-ares (asynchronous name resolver) to 1.31.0
- **Imp** Script language improvements
- Support to override encoding when doing [MIMEPart.setBody()](https://docs.halon.io/hsl/archive/6.3-stable/functions.html#MIMEPart.setBody) function
- New [strpduration()](https://docs.halon.io/hsl/archive/6.3-stable/functions.html#strpduration) function
- Updated libcurl (HTTP client library) to 8.8.0
- Default `cache []` scope is now `config` (in configuration version “6.3”) instead of `global` ([`scripting.cache.scope`](#config_startup:confval-scripting.cache.scope))
- **Imp** Command line tool improvements
- New [hqfmove](#cli_queue:cli-hqfmove) tool be able to migrate messages between hosts
- **Bug** Various bug fixes
- Fixed issue in halonctl when sorting a groupby
- Fixed issue with HalonMTA_message_getinfo() and HALONMTA_MESSAGE_TAGS
- Fixed memory leak when moving messages manually from the defer queue
- **Dep** Important changes
- Removed peer_cert in return value of smtp_lookup_rcpt()
## 6.2.4
Released 2024-05-14
- **Bug** Downgrade included c-ares package due to upstream regression
## 6.2.3
Released 2024-05-08
- **Bug** Fixed issue affecting CentOS7 due to bug in libc
## 6.2.2
Released 2024-05-02
- **Bug** Fixed regression in multi-field suspends with localip
## 6.2.1
Released 2024-05-02
- **Imp** Improved multicore performance on certain workloads
- **Bug** Fixed rare issue with certain types of dynamic suspends
## 6.2
Released 2024-04-26
- **Imp** MTA improvements
- Improved suspend and policy evaluation performance
- Added the ability to specify PROXY protocol servers per localip
- Added [`queues.threads.rate.priority`](#config_startup:confval-queues.threads.rate.priority) setting
- Added support for suspend flag in queue list and queue groupby
- Default rate algorithm for `"6.2"` policy configuration is now fixed-window instead of token-bucket (smtpd-policy.yaml: `rate: algorithm: fixedwindow/tokenbucket`)
- Added support for systemd notifications in `smtpd` process
- Ability to trace a specific transaction
- Added support for RFC8616 - EAI In DKIM and DMARC
- Added option for disabling announcing `PIPELINING` ([`servers[].extensions.pipelining`](#config_smtpd:confval-servers-.extensions.pipelining))
- Added check to enforce protocol synchronization (`compatibility.synchronization`)
- Added `spool.threads.loader.wait` setting
- Domain DNS cache may include nullmx
- C-ares upstream (1.28.1)
- **Imp** Script language improvements
- Improved detection of “Message expired” in Post-delivery script hook
- DNS resolving functions are now async (`dns_query`, `spf_query`, and `verifyDKIM`)
- `sleep` function is now async
- `signDKIM` now signs `List-Unsubscribe` headers by default
- Optimized `pcre_replace` replacements
- **Imp** Command line tool improvements
- Added colors to `halontop`
- Clarified usage of `--reason` argument in `halonctl` queue update commands
- `halonconfig` will now use stable sort when compiling [running configuration](#config_smtpd) (`smtpd-app.yaml`) includes
- **Bug** Various bug fixes
- Fix issue with conflicting Ansible roles (When running multiple roles on the same server)
- Remove leading spaces when unfolding headers with `MailMessage->getHeader()`
- Fixed issue where `csv_encode` omitted all empty columns at the start of the list
- **Dep** Important changes
- `MAIL FROM: <> SIZE` parameter is now enforced
- Removed `getpeercert` on `TLSSocket`
- Removed `dnsbl` function (use `dns_query` since 5.3)
- Removed `dns` and `dns(mx|txt|...)` functions (use `dns_query` since 5.1)
- Removed support for custom servers and timeout for `dns_query`, `spf_query`, and `verifyDKIM`
- Deprecated `peer_cert` in return value of `smtp_lookup_rcpt` (`peercert` should be used)
- In protobuf API rename `UNKNOWN` state to `OTHER`
- The `halonctl.yaml` starting from file version 6.2, does now use `smtpd.yaml` format (and path).
## 6.1.1
Released 2023-12-27
- **Imp** Explicitly configure bare LF behavior (`compatibility.barelf`)
- Default is changed to auto-detection, to keep the previous behavior set it to `true`.
## 6.1
Released 2023-12-14
- **Imp** Plugin API(s)
- [`HalonMTA_hsl_value_set()`](#plugins_native:c.HalonMTA_hsl_value_set) can now set an OpenSSL `EVP_PKEY*` to a `Privatekey` resource
- [`HalonMTA_hsl_value_type()`](#plugins_native:c.HalonMTA_hsl_value_type) can now detect `X509Resource` and `Privatekey` resources
- [`HalonMTA_hsl_value_get()`](#plugins_native:c.HalonMTA_hsl_value_get) can now get OpenSSL `X509*` and OpenSSL `EVP_PKEY*` from `X509Resource` and `Privatekey` resources
- Added a new import API
- [`HalonMTA_import_new()`](#plugins_native:c.HalonMTA_import_new)
- [`HalonMTA_import_abort()`](#plugins_native:c.HalonMTA_import_abort)
- [`HalonMTA_import_append()`](#plugins_native:c.HalonMTA_import_append)
- [`HalonMTA_import_commit()`](#plugins_native:c.HalonMTA_import_commit)
- **Imp** MTA improvements
- It’s now possible to set concurrency and rate as unlimited in queue policy conditions
- Added option to never try expired messages in queue (`transportgroups[].retry.pickupexpire`)
- Added support for changing syslog facility ([`environment.syslog.facility`](#config_startup:confval-environment.syslog.facility))
- Added support for groupings per IP ([`queues.grouping.groupings[].unique`](#config_smtpd:confval-queues.grouping.groupings-.unique))
- Added support for spool-in only queue folders ([`spool.paths[].write`](#config_startup:confval-spool.paths-.write))
- Added option to remove the from part of the Received header ([`servers[].phases.data.receivedheader.from`](#config_smtpd:confval-servers-.phases.data.receivedheader.from))
- Added support for [`servers[].proxy.xforwardedfor`](#config_smtpd:confval-servers-.proxy.xforwardedfor) header in HTTP API
- **Imp** Command line tool improvements
- Improved `--json-request` option so it includes the full request (including command)
- Added option to show queue trace with elapsed time
- Added possibility to update jobid in queue update command
- Added ability to delete suspends by filter
- Added ability to list and delete policy conditions by filter
- **Imp** Script language improvements
- Allow configuration of `sourceip` for `proxyprotocol`
- Added `localipid` to post-delivery connection logging object
- Added a nullish coalescing assignment operator to HSL (`??=`)
- Added more insights into generic delivery errors (such as DNS response code) in post-delivery
- Throw an exception (`"No such transportid"`) if message is queued on a non-existing transport
- Added `mx_exclude_bypass` to override any `mx_exclude`
- Added support for `identity` option to `additional_signatures` option in `signDKIM`
- Added `Map->merge()` function to merge keys
- Added “buffered” option to `File()` to disable buffering `file://` files into memory
- Added `mimeword_decode` and `mimeword_encode` functions
- Added `header_addresslist_compile` function to properly escape a list of addresses with display names
- `header_addresslist_extract` can now extract display names for addresses as well
- `http()`, `TLSSocket()`, `smtp_rcpt_lookup`, `smtp_rcpt_auth()`, `MIME.send()`, `MailMessage.send()`, `Predelivery.Try()` now support tls client certificate option as array of “x509”, “chain” (array) and “privatekey” resources
- `pkcs7_sign()` now support certificate option as array of “x509”, “chain” (array) and “privatekey” resources
- Improve overall performance of crypto functions using PKI or keys from the configuration
- Added `toResource` method on `X509` object
- Added support for importing certificates as resources in HSL
- Improved performance of halonconfig when used with large configurations
- **Dep** Important changes
- Revamped [license system](https://docs.halon.io/manual/install.html#license)
- The `cache [ per_message ]` option has now been removed as a no-op (deprecated in `6.0`)
- Private keys in the configuration (`pki[].private`) now needs to be in PEM format. In the case of ed25519 private keys without PEM header, use the following command `(printf \\x30\\x2e\\x02\\x01\\x00\\x30\\x05\\x06\\x03\\x2b\\x65\\x70\\x04\\x22\\x04\\x20 && cat ed25519.key | base64 -d) | openssl pkey -inform DER -outform PEM` to add PEM header.
## 6.0.3
Released 2023-11-15
- **Bug** Problem with eviction of connection based on grouping
## 6.0.2
Released 2023-10-02
- **Sec** Updated libspf to fix [CVE-2023-42118](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-42118)
## 6.0.1
Released 2023-09-01
- **Bug** Fix issue with OpenMetric output in *halonctl*
- **Bug** Suppress warning for unhandled submission.yaml file in *halonconfig*
- **Bug** HTTP2 wasn’t available in *http()* on Ubuntu 22.04
## 6.0
Released 2023-08-16
- **Imp** MTA improvements
- Overall performance and memory usage improved
- Ability to configure custom [thread pools](https://docs.halon.io/manual/archive/6.0-stable/config_startup.html#events) for internal SMTP event loops
- Allow pooling reuse by [grouping](https://docs.halon.io/manual/archive/6.0-stable/config_smtpd.html#confval-queues.pooling.policy) fields
- New C-ABI functions
- [HalonMTA_config_to_json()](https://docs.halon.io/manual/archive/6.0-stable/plugins_native.html#c.HalonMTA_config_to_json) to convert configuration to JSON
- Added two new API hooks [Halon_ready()](https://docs.halon.io/manual/archive/6.0-stable/plugins_native.html#c.Halon_ready) and [Halon_early_cleanup()](https://docs.halon.io/manual/archive/6.0-stable/plugins_native.html#c.Halon_early_cleanup)
- Added a new [inject API](https://docs.halon.io/manual/archive/6.0-stable/plugins_native.html#inject-plugin)
- Improvements to `halonctl`
- Added support for all metadata filter options in `halonctl queue` commands
- Added `--limit` option to queue update command
- Added `--probability` option to halonctl queue update, list and unload commands
- Added types and help to OpenMetric output for `halonctl process-stats` command
- Include CycloneDX SBOM files for the smtpd package
- Improvements to metadata queries in queue protobuf API
- Increase tag limits (16 to 32 characters) for queue pickup policies and suspends
- **Imp** Script language improvements
- Added `additional_fields` and `additional_recipient_fields` options to [Bounce()](https://docs.halon.io/hsl/archive/6.0-stable/postdelivery.html#Bounce) function
- Added `remotemta` override to `fields` options for [Bounce()](https://docs.halon.io/hsl/archive/6.0-stable/postdelivery.html#Bounce) function
- **Dep** Important changes
- Remote SMTP bounce messages no longer include SMTP states
- Removed special meaning of `lookup-mx` as hostname to `Try()`, `smtp_()` and `MIME|MailMessage.send()` functions
- Removed special meaning of `MIME("0")` (to get the EOD hooks root MailMessage object)
- The `cache [ per_message ]` option is now deprecated and will become a no-op in 6.1 (use `$context` variable instead)
- Removed builtin `LDAP()`, `ScanDLP()` and `ScanDMARC()` functions (use halon-extras-ldap, -dlp and -dmarc instead)
- **Bug** Various bug fixes
- HSL function [memory_store()](https://docs.halon.io/hsl/archive/6.0-stable/functions.html#memory_store) callback argument bug
## 5.12.1
Released 2023-05-03
- **Bug** In HSL fix an issue with Map/Sets iterators and foreach
## 5.12
Released 2023-04-12
- **Imp** MTA improvements
- Faster policy evaluations and subdomain matching
- Added a new
[grouping](https://docs.halon.io/manual/archive/5.12-stable/config_smtpd.html#confval-queues.grouping)
field to the queue structure to be used for combined
recipientdomain/remotemx groupings
- Added `LOGIN` parameter support to the `XCLIENT` command
- Added support for network matching in
[PROXY](https://docs.halon.io/manual/archive/5.12-stable/config_startup.html#confval-servers-.proxyprotocol),
[XCLIENT](https://docs.halon.io/manual/archive/5.12-stable/config_smtpd.html#confval-servers-.extensions.xclient)
and [TLS client
cert](https://docs.halon.io/manual/archive/5.12-stable/config_smtpd.html#confval-servers-.tls.clientcert)
settings
- Added connection pooling reuse
[setting](https://docs.halon.io/manual/archive/5.12-stable/config_smtpd.html#confval-queues.pooling.policy)
based on remotemx
- Various improvements to DNS resolving
- Added a global DNS domain cache
- Restructured DNS cache to resolve all addresses, with
deduplication
- Added global settings for
[resolver.mx.exclude.ip](https://docs.halon.io/manual/archive/5.12-stable/config_smtpd.html#confval-resolver.mx.exclude.ip)
and
[resolver.mx.exclude.mx](https://docs.halon.io/manual/archive/5.12-stable/config_smtpd.html#confval-resolver.mx.exclude.mx)
- Priority messages now also have priority in resolver and script
execution queues
- Allow overriding serverhost (`servers[].hostname`) in connect
hooks’s `Accept()` function
- Added ability to read plugin configurations from file paths
- Improvements to `halonctl`
- Added `--no-pipelining` options to queue trace command
- More precise time (usec) resolution in queue trace command
- Queue update command now supports changing only localpart or
domain of senders/recipients
- `halontop` now resizes layout to terminal size
- Added a new C-API
([HalonMTA_deliver_trace](https://docs.halon.io/manual/archive/5.12-stable/plugins_native.html#c.HalonMTA_deliver_trace))
to do custom trace outputs in delivery plugins
- Added a new C-API for [queue
pickup](https://docs.halon.io/manual/archive/5.12-stable/plugins_native.html#c.Halon_queue_pickup_acquire2)
plugins
- **Imp** Script language improvements
- Added support for multiline CSV data in
[csv_encode()](https://docs.halon.io/hsl/archive/5.12-stable/functions.html#csv_encode)
and
[csv_decode()](https://docs.halon.io/hsl/archive/5.12-stable/functions.html#csv_decode)
- Added option to allow non-convertible objects to be converted to
none in
[json_encode()](https://docs.halon.io/hsl/archive/5.12-stable/functions.html#json_encode)
- **Dep** Important changes
- Deprecated `ScanDMARC()` (install `halon-extras-dmarc`)
- Deprecated builtin `LDAP` class (install `halon-extras-ldap`)
- Removed `MailMessage.deliver` (deprecated since 5.2, use
`MailMessage.queue()` instead)
- Removed `/dev/null` as special transport destination
- Removed `GetMailQueueMetric()` (deprecated since 5.3)
- **Bug** Various bug fixes
- Missing dynamic policy id in `$arguments` in post-delivery on
rate only policy conditions
- Fixed timeout issue with PIPELINING
- `queue_policy_delete()` function was missing in AUTH hook
- Fix issue with `unset` in `foreach` loops
- Fix issue with `scripting.rootpath` ending with `/`
- In HSL do not allow index dereference of string with key types
other than numbers
- In HSL do not allow slice operator dereference with key types
other than numbers
- `jobid` set in pre-delivery `Try()` function was not updated
in post-delivery hook
- Fixed rare crash with queue trace command on busy systems
## 5.11.3
Released 2023-02-17
- **Bug** Improved error handling with HTTP submissions API
- **Bug** Fix error message with `halonconfig --dist-check` and
missing schemas
- **Bug** Fix rare issue connection retry
## 5.11.1
Released 2023-02-06
- **Bug** Fix a rare issue with configuration reload on Ubuntu
## 5.11
Released 2023-02-03
- **Imp** MTA improvements
- Added a `halonctl queue trace` command to trace outbound SMTP
connections
- Outbound PIPELINING support
- Ability to change TLSv1.3 cipher suites on server and clients
- Queue performance optimizations
- Added
[setDateLater()](https://docs.halon.io/hsl/functions.html#MIMEPart.setDateLater)
function to set Date header on delivery
- Added connection `id` and numbers of `transactions` to
[post-delivery](https://docs.halon.io/hsl/postdelivery.html)
attempt connection array
- Added support for networks in the
[Try()](https://docs.halon.io/hsl/predelivery.html?highlight=ip_exclude#Try)
argument `ip_exclude`
- Allow changing jobid with `Queue()` functions in pre- and
post-delivery
- Added remotemx information to outbound connection list
- Added support for `--size`, `--subject`, `--metadata-and`
filter conditions to `halonctl`
- Added the ability to clear the internal DNS cache using domain
wildcards in `halonctl`
- **Imp** Script language improvements
- `Map`/`Set` iterator are now thread safe with imports
(modifications are not)
- Added support for [data
types](https://docs.halon.io/hsl/structures.html#data) when
`import`ing YAML files
- Added support for private key
[import](https://docs.halon.io/hsl/structures.html#data)
(RSA and ED25519 keys)
- Added
[rsa_privatekey()](https://docs.halon.io/hsl/functions.html#rsa_privatekey)
and
[ed25519_privatekey()](https://docs.halon.io/hsl/functions.html#ed25519_privatekey)
function to load keys as PrivateKey resources
- Added
[array_combine()](https://docs.halon.io/hsl/functions.html#array_combine)
function
- Added `exclude_headers` to `signDKIM()` functions
- Added support for PrivateKey resources to `signDKIM()` functions
- Added support for
[tags](https://docs.halon.io/hsl/functions.html#yaml_decode)
when `parsing`ing YAML files
- Added `MIMEPart.getByID()` functions to retrieve a MIMEPart by ID
- Added support for Regex types to `MIMEPart.findByType()` and
`MIMEPart.findByFileName()` functions
- `csv_encode()` will not added quotes to string with spaces
- **Bug** Fix issue with slow graceful shutdown of `smtpd`
- **Bug** Fix issue with the `halonctl queue groupby` command if
jobid was set with `Try()`
- **Bug** Fix issue with including files using symlinks within the
rootpath in HSL
- **Bug** Fix issue with global functions and module imports in HSL
- **Dep** Important changes
- Migrating away from Cyren (ctasd and ctipd)
- The deprecated `DirectDeliver()` function now acts as a queuing
deliver
- Removed deprecated `GetTLS()`, `Allow()`, `Block()` and
`DeliverWithDKIM()` functions
- On Linux, trying to reload with Halon script compilation errors
will continue using the previous configuration
## 5.10.1
Released 2022-12-06
- **Bug** halonctl resolver command didn’t ignore SIGPIPE
- **Bug** halontop didn’t show patch version properly
- **Bug** On some Linux distros environment.rlimit.nice could not
be set to a negative value
- **Bug** Improved error handling on JSON payloads in smtpd HTTP
API
- **Bug** On configuration load filename was missing in error
message
- **Bug** Fixed a stability issue
## 5.10
Released 2022-10-11
- **Imp** Package and repository improvements
- Builds for Ubuntu 22.04 LTS
- Packages for [extras](http://github.com/halon-extras) projects
written in Halon script
- **Imp** MTA improvements
- Ability to change IP address family (v4/v6) preference
- Support for [implicit
TLS](https://docs.halon.io/manual/config_smtpd.html#confval-transportgroups-.session.tls.implicit)
for outbound connections
- New outbound TLS mode
[dane_fallback_require_verify](https://docs.halon.io/hsl/predelivery.html#Try)
for DANE/MTA-STS coexistence
- Added peer certificate error and TLSA to [post-delivery’s
attempt](https://docs.halon.io/hsl/postdelivery.html#tls)
information
- Store last attempt’s `localip`, `remoteip` and `remotemx` in
the defer queue
- **Imp** Script language improvements
- Added
[domain_publicsuffix()](https://docs.halon.io/hsl/functions.html#domain_publicsuffix)
function
- Added
[url_parse()](https://docs.halon.io/hsl/functions.html#url_parse)
function
- Added `insert_function` option to `cache []`
- Added new
[match](https://docs.halon.io/hsl/structures.html#match)
expression
- `Socket.recv` may now use multiple flags as an array
- Added support for `body_length` in
[signDKIM()](https://docs.halon.io/hsl/functions.html#MailMessage.signDKIM)
- Ability to set properties and the stop flag in for dynamic
[queue_policy()](https://docs.halon.io/hsl/functions.html#queue_policy)
- **Imp** Protobuf API improvements
- `QueueGroupBy` by `localip` as well
- `QueueGroupBy` support policy grouping (MX rollup, etc)
- More `ServerConnectionsList` information; transactions, local IP
and ports
- **Imp** Command line tool improvements
- `halonctl groupby` overhaul, adding lots of new functionality
- `halonctl queue update` now have a dry-run flag to test queries
- `halonctl process-stats` may now output Prometheus compatible
formats
- **Bug** Fix issue with dynamic policies in the 35/8 subnet
- **Dep** [Important
changes](https://docs.halon.io/go/releasenotes510)
- The Ubuntu packages no longer depends on `halon-extras-rate` and
`halon-extras-dlp` (need to be explicitly installed)
- The per-recipient end-of-DATA function `DirectDeliver` is
deprecated, use `MailMessage.send()` instead
- The post-delivery function `GetTLS()` is deprecated, use the
`tls` object in `$arguments` instead
## 5.9.3
Released 2022-12-05
- **Bug** Fixed a stability issue
## 5.9.2
Released 2022-08-16
- **Bug** `GetTLS()` in post-delivery could fail on non-delivered
messages
- **Bug** Issue with `Queue()` in post-delivery with no retry
delay
- **Bug** Fixed an error message in `halonconfig`
## 5.9.1
Released 2022-05-31
- **Bug** Fix issue with IPC (Unix domain socket) permission on
Linux
## 5.9
Released 2022-05-30
- **New** Added
[priority](https://docs.halon.io/hsl/eodonce.html#EODMailMessage.queue)
for queued messages
- **New** Added max age of messages in queue (`retry.during`) as
an alternative to `retry.count`
- **Imp** MTA improvements
- Added support for thread priority for various thread pools and
event loops
- Made `ip_exclude_temporary` also work with `mx_exclude` in
SMTP clients
- Added `dane_fallback_require` as TLS mode in SMTP clients
- Added support for more AUTH mechanisms in SMTP clients (eg.
XOAUTH2)
- Randomize the order of equal-preference MX host addresses
- Added possibility to configure custom script thread pools per hook
- Delivery settings may now change max retries/during and retry
intervals
- Added `queues.maxmessages` to limit number of messages to hold
in memory
- Added `data.fixheaders` (false) and `data.mimepart.fixheaders`
(false) to inject `\r\n` before bad headers
- Added `queue.loader.active` counter to hold total messages
currently loaded
- Postmaster address may be configured separately as localpart and
domain (inherit from reporting-mta)
- Added possibility to disable MIME multipart parsing for
performance
- Added `maxparts` to configure max MIME parts
- Added ability to change `transportid` in `Try()`
- Added `$arguments["expired"]` to post-delivery hook to indicate
bounces due to age or max retry count
- Added reason to `Delete()` in the pre-delivery hook
- Added support for DSN options to `Bounce()` in the pre-delivery
hook
- **Imp** Script language improvements
- Added array dereference with
[spread](https://docs.halon.io/hsl/archive/5.9-stable/operators.html#spread)
operator inside of arrays
- Allow upcast of TLSSocket to Socket with
[TLSocket.toSocket()](https://docs.halon.io/hsl/archive/5.9-stable/functions.html#TLSSocket.toSocket)
- Added
[setFileName()](https://docs.halon.io/hsl/archive/5.9-stable/functions.html#MIME.setFileName)
and
[setDisposition()](https://docs.halon.io/hsl/archive/5.9-stable/functions.html#MIME.setDisposition)
functions to `MIME()`
- Detect MIME parsing errors with
[MailMessage.getErrors()](https://docs.halon.io/hsl/archive/5.9-stable/functions.html#MIMEPart.getErrors)
- Added support to create `MailMessages` from `File` objects
using
[MailMessage::File()](https://docs.halon.io/hsl/archive/5.9-stable/functions.html#MailMessage.File)
- Added reason for policy result and detailed error/errno to
`spf_query()` output
- **Imp** Native (C ABI) plugin improvements
- Added support to get object properties with
[HalonMTA_hsl_object_property_get()](https://docs.halon.io/manual/archive/5.9-stable/plugins_native.html#c.HalonMTA_hsl_object_property_get)
- Create MailMessage and File classes from strings with
[HalonMTA_hsl_value_set()](https://docs.halon.io/manual/archive/5.9-stable/plugins_native.html#c.HalonMTA_hsl_value_set)
- Detect MailMessage type with
[HalonMTA_hsl_value_type()](https://docs.halon.io/manual/archive/5.9-stable/plugins_native.html#c.HalonMTA_hsl_value_type)
- **Imp** HTTP submission API improvements
- Added `/health` endpoints to be used with load balancers
- Added `$arguments["headers"]` to end-of-DATA script
- Added concurrency limit setting per server
- Certificates are soft reloadable in configuration
- **Imp** Protobuf API improvements
- Include HTTP connection in ServerConnectionsList replies
- Support for close reason of HTTP connections with
ServerConnectionsClose
- Added option to reset `transactionid`, `ts` and `retrycount`
upon message import
- Bounce action may set `status` and `diagnosticcode` on queue
updates
- Added regular expression support in StringMatch in API for message
conditions
- Added unique count feature to groupby
- Added maximum values to process stats and present them nicely in
`halontop`
- **Imp** All halonctl commands accepting time now allows for
X[dhms] syntax
- **Imp** Added dist-check command to halonconfig to check
generated configuration
- **Imp** Added option to stop-on-match in smtpd-policy.yaml when
matching rules
- **Imp** Add configurable rate sync delay to improve UDP
synchronization reliability
- **Bug** Fix validation bug in JSON schema with HTTP submission
API
- **Bug** Fix validation bug in post-delivery hook with
`$arguments["dsn"]["status", "diagnost`…
- **Bug** Allow long lines in message body when receiving messages
with CHUNKING
- **Bug** Fix rate issue with dynamic policies which could cause
MTA restarts
- **Bug** HTTP submission API header name “X-API-Key” was not case
insensitive
- **Bug** Statistics queue.policy.rate.suspends not decreased on
manual message deletes
- **Bug** Fix bug in C API when invoking function pointers in class
member functions
- **Bug** Properly detect temperror in ScanDMARC()
- **Bug** Fix escaping of email addresses by escaping additional
characters
- **Bug** Added detection newer versions of .docx and .xlsx files
in ScanDLP()
- **Dep** Deprecated use of MIME(“0”) in EOD, use
$arguments[“mail”] or GetMailMessage() instead
- **Dep** [Important
changes](https://docs.halon.io/go/releasenotes59)
- Removed “eodrcpt” from Linux builds
- Removed ability to control “rated” and “dlpd” from “halonctl” (use
ratectl or dlpctl instead)
- Removed deprecated functions from per-message end-of-DATA script:
Queue(), GetMailMessage(), GetTLS(), GetAddressList(), DKIMSign(),
DKIMSDID(), DKIMVerify()
## 5.8.4
Released 2022-03-16
- **Sec** Updated OpenSSL to fix
[CVE-2022-0778](https://www.freebsd.org/security/advisories/FreeBSD-SA-22:03.openssl.asc)
## 5.8.3
Released 2022-02-10
- **Bug** Auto-detection of DKIM keys in `additional_signatures`
fixed
- **Bug** Reduce risk of `rated` UDP packet loss for initial
synchronization
- **Bug** Issue with `ScanDLP()` and custom rules fixed
- **Bug** Updated libcurl to 7.81
## 5.8.2
Released 2022-01-25
- **Bug** DKIM validation issue under certain circumstances
## 5.8.1
Released 2022-01-10
- **Bug** Privdrop after creating control socket in `rated`
- **Bug** Fix `rated` startup rate hit sync
- **Bug** Fix minor issue parsing bad headers
## 5.8
Released 2021-12-13
- **New** Templates for [Docker and
Kubernetes](https://github.com/halon/halon-docker)
- The `dlpd` content filtering connection now uses a HTTP based
API
- The `rated` rate control connection supports DNS with dynamic
re-resolve
- **New** APT/deb and RPM
[repositories](https://docs.halon.io/manual/archive/5.8-stable/comp_install.html)
- Components are now in separate packages (MTA, rate control,
content inspection, etc.)
- Added separate `dlpctl` an `ratectl` tools for the respective
new packages
- **New** MTA features
- [HTTP
submission](https://docs.halon.io/manual/archive/5.8-stable/http_submission.html)
API for pre-formatted RFC822 messages
- [halontop](https://docs.halon.io/manual/archive/5.8-stable/cli.html#halontop)
program showing realtime process metrics
- Faster DKIM dual-signing with `additional_signatures` to
[signDKIM()](https://docs.halon.io/hsl/archive/5.8-stable/functions.html#MIME.signDKIM)
- Ability to use multiple [spool
paths](https://docs.halon.io/manual/archive/5.8-stable/config_startup.html#confval-spool.paths-.path)
- Added support
[reserved](https://docs.halon.io/manual/archive/5.8-stable/config_smtpd.html#confval-servers-.concurrency.reserved.total)
connection slots
- Export message using with `QueueExportRequest`
[API](https://docs.halon.io/manual/archive/5.8-stable/api_reference.html#command-list)
- **New** Native (C ABI) plugin features
- Added
[Halon_command_execute()](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#c.Halon_command_execute)
function, supporting `argv` style of arguments
- Added
[Halon_early_init()](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#c.Halon_early_init)
function, supporting early initialization
- Added
[HalonMTA_hsl_value_to_json()](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#c.HalonMTA_hsl_value_to_json)
and
[HalonMTA_hsl_value_from_json()](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#c.HalonMTA_hsl_value_from_json)
functions
- Added
[HalonMTA_hsl_value_array_length](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#c.HalonMTA_hsl_value_array_length)
function
- [Autoloading](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html)
plugins without configuration
- Support for
[objects](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#objects)
- Support for
[exceptions](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#c.HalonMTA_hsl_throw)
- Support for
[static](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#c.HalonMTA_hsl_register_static_function)
functions
- Support for running [Halon script
functions](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#callable)
in C
- Support for working with
[lists](https://docs.halon.io/manual/archive/5.8-stable/plugins_native.html#lists-plugin)
function in C
- Support for more types in C API such as `X509`, `File` objects
- **Imp** Script language improvements
- Added
[zlib_compress()](https://docs.halon.io/hsl/archive/5.8-stable/functions.html#zlib_compress)
and uncompress functions
- Added
[MIME.toFile()](https://docs.halon.io/hsl/archive/5.8-stable/functions.html#MIME.toFile)
function
- Added
[MIME.setDate()](https://docs.halon.io/hsl/archive/5.8-stable/functions.html#MIME.setDate)
function
- Added
[X509.extensions()](https://docs.halon.io/hsl/archive/5.8-stable/functions.html#X509.extensions)
function
- Added
[X509.verify()](https://docs.halon.io/hsl/archive/5.8-stable/functions.html#X509.verify)
function
- Added
[X509::String()](https://docs.halon.io/hsl/archive/5.8-stable/functions.html#X509.String)
function
- Added
[queue_policy_delete](https://docs.halon.io/hsl/archive/5.8-stable/functions.html#queue_policy_delete)
function
- Added
[import](https://docs.halon.io/hsl/archive/5.8-stable/structures.html#data)
support for multiple X509 certificates (.crt)
- Added
[import](https://docs.halon.io/hsl/archive/5.8-stable/structures.html#data)
support for rfc822 message files (.eml)
- Added
[glob import](https://docs.halon.io/hsl/archive/5.8-stable/structures.html#wildcard-file-imports)
support
- Added support for `srv` records in
[dns_query()](https://docs.halon.io/hsl/archive/5.8-stable/functions.html#dns_query)
function
- **Imp** MTA improvements
- Improved DSN generation by supporting to include full original
messages and DSN field customization
- Include DSN arguments in post-delivery `$arguments["dsn"]` array
- Include queue policy insights in post-delivery
`$arguments["policy"]` array
- Added support for customization of the Received header (`tls`
and `for`)
- Order queue list by retrycount
- Support message retry with a jitter (distribution) in queue
actions
- Added support for queue message grouping of remoteip and remotemx
- Added lists, grouping of conditions, and custom properties to
active queue policy and delivery settings
- Allow configuration of multiple
[pickup](https://docs.halon.io/manual/archive/5.8-stable/config_startup.html#confval-queues.threads.pickup)
threads
- Added [delivery
counters](https://docs.halon.io/manual/archive/5.8-stable/cli.html#halonctl)
to process stats
- Added support for `.halonignore` files when packaging
configuration using `halonconfig`
- **Bug** Fix bug with localip in smtpd-delivery.yaml
- **Dep** [Important
changes](https://docs.halon.io/go/releasenotes58)
- Removed the deprecated `GetMailFile()` function
- It’s no longer valid to send message to plain IP addresses
`user@ip`
- Standard ciphers names are now used in logs (instead of OpenSSL
convention)
- Graceful shutdown for inbound connections
- Renamed process threads (visible in `top`)
## 5.7.4
Released 2021-11-09
- **Bug** MIME formatting issue with append/prependPart() on
non-MIME messages
- **Bug** Issue with DKIM signature in bounces
- **Bug** `MIME()` not storing decoded headers when queuing
## 5.7.3
Released 2021-09-19
- **Bug** Direct deliver in EOD rcpt hook
## 5.7.2
Released 2021-08-24
- **Bug** File type detection in DLP engine
## 5.7.1
Released 2021-08-06
- **Bug** Make linter recognize `$connection["tls"]["sni"]`
- **Bug** Startup bulk synchronization in `rated`
- **Bug** PROXY protocol heartbeat connections
## 5.7
Released 2021-07-21
- **New** Introducing [exception
handling](https://docs.halon.io/hsl/archive/5.7-stable/functions.html?#_CPPv29Exception)
with accompanying `try`, `catch` and `throw` control structures
- **New** [Visual Studio Code
plugin](https://docs.halon.io/manual/vscode.html) support for
debugging script in the `smtpd` MTA process
- **New** `ServerConnectionsListRequest` API
- **New** Ability to close inbound connections with
`ServerConnectionsCloseRequest` API
- **New** Added
[array_unique()](https://docs.halon.io/hsl/archive/5.7-stable/functions.html?#array_unique)
and `array_shuffle()` functions
- **New** Added
[pcre_compile()](https://docs.halon.io/hsl/archive/5.7-stable/functions.html?#pcre_compile)
function to pre-compile regular expressions from user input
- **New** Added
[#/myregexpattern/](https://docs.halon.io/hsl/archive/5.7-stable/literals.html#regex)
syntax for creating pre-compiled regular expressions
- **New** Added
[X509::String()](https://docs.halon.io/hsl/archive/5.7-stable/functions.html#X509.String)
method for creating an X.509 resource from PEM or DER
- **New** Added
[MIME.send()](https://docs.halon.io/hsl/archive/5.7-stable/functions.html#MIME.send)
method for inline delivery directly from MIME object
- **Imp** New HQF2 queue file format which combines .eml and .hqf
- **Imp** Added `--unpack` option to `halonconfig` to reverse
packing
- **Imp** The `halonconfig` command now validates plugin
configuration schemas
- **Imp** Added `additional_headers` option to pre-delivery
`Try()` function for passing data
- **Imp** Added `modified_original` DSN option to post-delivery
`Bounce()` for altering original headers
- **Imp** Added connection `id` and `remoteuid` to SMTP server
`$connection` array
- **Imp** Added `remove_if_zero` argument to `memory_dec()` for
automatic cleanup
- **Imp** Added `json` and `regex` formatters to `import` and
`csv_decode()`
- **Imp** Ability to `import` text file lines as `Set` object
items for fast lookups
- **Imp** Allow `foreach` on `$this` in classes to iterate all
instance properties
- **Imp** Added `depth` option to `domain_includes()` function
- **Imp** Ability to use `response_headers` with
`extended_result` in `http()`
- **Imp** Support for loading `File` from standard input when
running in `hsh`
- **Dep** The `=~` and `!~` operators now throws on invalid
regular expressions
- **Dep** Messages with empty `remotemx` or `jobid` are not
matched against queue policies with those
- **Dep** Modules loaded with `import` can no longer access
global user-defined functions
- **Dep** Removed `mail()` option `rawbody` and URI fallback
for `ldap_bind()` (deprecated since 4.8)
## 5.6.4
Released 2021-07-19
- **Bug** No longer passes function pointers from in `$context`
(only data is supported)
- **Bug** Fixed long loading times of `hsl-lint` and
`halonconfig` with large YAML files
- **Bug** Resolved issue with `queue_suspend()` and
`queue_policy()` argument order
- **Bug** Connection pooling didn’t work with outbound PROXY
protocol
- **Bug** In post-delivery attempt array, localip was not set
correctly with outbound PROXY protocol
- **Bug** Issue with `cache []` size if is set to `2**32`
- **Bug** The transport retry intervals didn’t override the
transport group’s intervals
## 5.6.3
Released 2021-06-29
- **Bug** Issue with the `cache []` when exception was thrown
from cached function
- **Bug** Context switching with plugins wasn’t supported in the
PROXY script
- **Bug** The `$context` variable wasn’t updated after the EOD
script hook
- **Bug** Only one DKIM signature was supported on the `MIME()`
object
- **Bug** Imported function could not resolve nested function in
some special cases
- **Bug** Imported object between multiple hooks could not resolve
imported functions in some special cases
## 5.6.2
Released 2021-05-28
- **Bug** Fixed issue with updated variables and logging in
per-recipient end-of-DATA script
- **Bug** Fixed auto generation of transaction ID on API
`QueueImport`
- **Bug** Fixed encoding issue in `xtext_encode()`
## 5.6.1
Released 2021-04-08
- **Bug** Fixes a race condition in the queue script
## 5.6
Released 2021-03-22, see the [release
notes](https://docs.halon.io/go/releasenotes56) for notable changes
- **New** Added native
[plugin](https://docs.halon.io/manual/archive/5.6-stable/plugins_native.html)
support by loading C ABI compatible libraries
- **New** Added CSV schema validation support in
[import](https://docs.halon.io/hsl/archive/5.6-stable/structures.html#data)
so that imported files gets validated
- **Imp** Ability to start the server without any listeners in
order to drain queue
- **Imp** Allow server control socket to listen on IP in addition
to socket file
- **Imp** Ability to group queue distribution view by sending
domain
- **Imp** Added variables for [disconnect
reason](https://docs.halon.io/hsl/archive/5.6-stable/disconnect.html#arguments)
to disconnect script hook
- **Imp**
[http()](https://docs.halon.io/hsl/archive/5.6-stable/functions.html#http)
function now supports explicit `http_version` and `post_size`
- **Imp** Ability to modify (rather than just add and delete) queue
suspend and policy items
- **Imp** Added total count in API response for queue `list` and
`groupby` CLI/API commands
- **Imp** Possibility to disable the Received header (instead of
having to delete it from script)
- **Imp** Added In-Reply-To to DSN messages (to support
[threaded](https://docs.halon.io/hsl/archive/5.6-stable/predelivery.html#Try)
bounces)
- **Imp** Added decode option to
[MIMEPart.getHeader](https://docs.halon.io/hsl/archive/5.6-stable/functions.html#MIMEPart.getHeader)
functions
- **Imp** Added binary option to all hashing functions (eg.
[sha2](https://docs.halon.io/hsl/archive/5.6-stable/functions.html#sha2)
function)
- **Imp** Improved concurrent cache miss behavior in the HSL
[cache](https://docs.halon.io/hsl/archive/5.6-stable/structures.html#cache)
- **Imp** Added possibility to disable
[hook](https://docs.halon.io/manual/archive/5.6-stable/config_smtpd.html#confval-servers%5B%5D.logging.hook)
logs
- **Imp**
[Default()](https://docs.halon.io/hsl/archive/5.6-stable/postdelivery.html#Default)
function now allows configuration of bounce settings
- **Imp**
[Try()](https://docs.halon.io/hsl/archive/5.6-stable/predelivery.html#Try)
function IP include and excludes (similar to `mx_include` and
`mx_exclude`)
- **Bug** Resolved an issue with the queue updates
- **Bug** Resolved an issue with the import statements and reloads
- **Bug** Fix bug with paging of Halon script rates
- **Bug** Fix a memory leak in the backend authentication script
(with modules)
- **Bug** Missing error counters on connect and disconnect
- **Bug** Halon script Socket classes’ close() method could cause
unexpected disconnect behaviors
- **Bug** Fix support for BDAT and the proxy script hook
- **Bug** Fix an issue with SNMP and the firewall script
- **Dep**
[queue_suspend()](https://docs.halon.io/hsl/archive/5.6-stable/functions.html#queue_suspend)
and
[queue_policy()](https://docs.halon.io/hsl/archive/5.6-stable/functions.html#queue_policy)
has changed its return value
- **Dep** Text log formats has been slightly changed
- **Dep**
[File.toFFIValue()](https://docs.halon.io/hsl/archive/5.6-stable/functions.html#File.toFFIValue)
function now returns C-compatible FILE pointer.
- **Dep** Remove functions a per the
[deprecation](https://docs.halon.io/go/releasenotes56) note
- **Dep** Queue suspend and policy API calls return UUIDs instead
of `uint64`
- **Dep** Minimum
[supported](https://docs.halon.io/manual/archive/5.6-stable/api_reference.html#version-compatibility)
API version is (5.6)
## 5.5
Released 2020-11-16, see the [release
notes](https://docs.halon.io/go/releasenotes55) for notable changes
- **New** [DSN
extension](https://support.halon.io/hc/en-us/articles/360017337479)
support
- **New** [Outbound PROXY
protocol](https://support.halon.io/hc/en-us/articles/360017394679)
support
- **New** [Function
generators](https://docs.halon.io/hsl/archive/5.5-stable/structures.html#id11)
and `yield`
- **New**
[Map()](https://docs.halon.io/hsl/archive/5.5-stable/functions.html#Map)
and `Set()` data container classes
- **New** Ability to disable
[sourceip_random](https://docs.halon.io/hsl/archive/5.5-stable/predelivery.html#Try),
useful during for example IP warmup
- **New** Added
[halonctl hsl memory list](https://docs.halon.io/manual/archive/5.5-stable/cli.html#shared-memory-functions)
and corresponding `HSLMemoryListRequest` API
- **New** Added
[base32_encode](https://docs.halon.io/hsl/archive/5.5-stable/functions.html#base32_encode)
and decode functions
- **New** Added
[url_encode](https://docs.halon.io/hsl/archive/5.5-stable/functions.html#url_encode)
and decode functions
- **New** Test command for the asynchronous DNS resolver
[halonctl resolver query](https://docs.halon.io/manual/archive/5.5-stable/cli.html#dns-resolver)
- **New** Queue suspend filters with
`halonctl queue suspend list` and `SuspendRequest` API
- **New** Settings for [max
messages](https://docs.halon.io/manual/archive/5.5-stable/config_smtpd.html#confval-servers%5B%5D.phases.mailfrom.maxmessages),
recipients and hops
- **New** Added `unix_socket_path` option to
[http()](https://docs.halon.io/hsl/archive/5.5-stable/functions.html#http)
function
- **New** Added
[spool.corrupt](https://docs.halon.io/manual/archive/5.5-stable/config_startup.html#confval-spool.corrupt)
setting for controlling handling of bad queue files
- **New** High-resolution process runtime counter
`process.elapsed`
- **New** Active queue performance counters `queue.pickup.X`
- **New** Script error counters `servers.X.scripts.Y.errors`
- **Imp** Fraction of second in
[sleep()](https://docs.halon.io/hsl/archive/5.5-stable/functions.html#sleep)
- **Imp** Improved performance for large active queue policy rate
buckets and exclude lists
- **Imp** Ability to specify an ID for dynamic active queue
suspensions and policy conditions
- **Imp** Ability to filter `halonctl hsl rate list` based on
conditions
- **Imp** More relaxed rules for naming of ID and file path in YAML
schemas
- **Imp** Improved `smtpd` process shutdown
- **Imp** Improved SMTP command diagnostics (“in reply to”) in
bounces
- **Imp** Added Original-Envelope-ID and Original-Recipient headers
to bounces
- **Imp** Consider all 2xx-codes successful in SMTP client
- **Imp** File descriptor exhaustion error logging and handling
improved
## 5.4.3
Released 2020-11-09
- **Bug** Support binary POST data in background `http()` calls
- **Bug** Fixed MIME’s `verifyDKIM()` after doing message
modification
- **Bug** Fixed error flag issue with consecutive calls to
`ScanKAV()` (on same message)
- **Bug** Prevent announcing extensions from HELO/EHLO script hook
on `HELO`
- **Bug** Fixed `halonctl` missing fields `senderhelo`,
`saslusername` and `duration`
- **Bug** Fixed issue with queue list duration sort order
## 5.4.2
Released 2020-10-01
- **Bug** Fixed issue with PROXY protocol and implicit TLS
- **Bug** Fixed proxy script and lint issue with `halonconfig`
- **Bug** Fixed counting issue with the
`scripts.proxy.running`/`finished` process stats
- **Bug** Restored the `id` and `ts` fields in `ScanDMARC()`
output
- **Bug** Enable queuing of messages from `hsh`
## 5.4.1
Released 2020-08-14
- **Imp** [UUID
version](https://docs.halon.io/manual/config_startup.html#confval-environment.uuid.version)
selector (time/v1 or random/v4)
- **Bug** Fix default enhanced status code in bounces for certain
errors
- **Bug** Added validation of certain
[CLI](https://docs.halon.io/go/clitool) arguments to improve error
reporting
## 5.4
Released 2020-07-22, see the [release
notes](https://docs.halon.io/go/releasenotes54) for notable changes
- **New** CHUNKING/BDAT and the SIZE service extension
- **New** [Disconnect
script](https://docs.halon.io/hsl/archive/5.4-stable/disconnect.html)
hook
- **New**
[yaml_decode()](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#yaml_decode)
function and [import
loader](https://docs.halon.io/hsl/archive/5.4-stable/structures.html#data)
(similar to JSON)
- **New** Ability to export an File classes as a [C++
std::istream](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#File.toFFIValue)
- **New** Ability to export an X.509 resources as a [OpenSSL
pointer](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#X509.toFFIValue)
- **New** [FFI
callback](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#FFI.callback)
function
- **New** [DSN
options](https://docs.halon.io/hsl/archive/5.4-stable/postdelivery.html#Bounce)
to `Bounce()` and `Queue()` that allows overriding content,
headers, delay, etc
- **New** Pre-defined variable of the [DSN that is to be
generated](https://docs.halon.io/hsl/archive/5.4-stable/postdelivery.html#d1)
- **New** `Default()` function in post-delivery to [terminate
script](https://docs.halon.io/hsl/archive/5.4-stable/postdelivery.html#Default)
based on delivery result and settings
- **New** Settings for minimum amount of free
[inodes](https://docs.halon.io/manual/archive/5.4-stable/config_startup.html#confval-spool.minfree.inodes)
and bytes for receiving email
- **New** Setting for
[fsync](https://docs.halon.io/manual/archive/5.4-stable/config_startup.html#confval-spool.fsync)
before 250 on DATA
- **New** Settings for number of [max
hops](https://docs.halon.io/manual/archive/5.4-stable/config_smtpd.html#confval-servers%5B%5D.phases.data.maxhops)
(loop protection)
of 30 seconds
- **New** Pre-defined [variable for
username](https://docs.halon.io/hsl/archive/5.4-stable/connect.html#v-a1)
when accepting email on local Unix socket
- **New** `http()` [callback
function](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#http)
for POST data
- **New** MailMessage
[queue()](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#MailMessage.queue)
function
- **New**
[MailMessage](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#_CPPv211MailMessage)
`get/setPreamble()` and `get/setEpilogue()` functions
- **New** MailMessage `modifyContent()` function advanced,
[direct
modification](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#MailMessage.modifyContent)
of email data
- **New** Static `File::read()` function that [returns all
data](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#File)
from a file path
- **New** LDAP class
[getoption()](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#LDAP.getoption)
function
- **New** Numeric
[separator](https://docs.halon.io/hsl/archive/5.4-stable/literals.html#number)
- **New** [Zero-fill right
shift](https://docs.halon.io/hsl/archive/5.4-stable/operators.html#bitwise)
bitwise operator
- **Imp** Ability to change `import` loader type
- **Imp** Includes SIZE= in MAIL FROM if server announces SIZE
service extension
- **Imp** SMTP client always send EHLO first (and then try HELO)
- **Imp** Do not include detail in local-part in license count
([user+detail@domain](mailto:user+detail@domain))
- **Imp** MIME `queue()` [options for delay and
hold](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#MIME.queue)
- **Imp** Ability to override recipient domain for MX lookup in
[pre-delivery](https://docs.halon.io/hsl/archive/5.4-stable/predelivery.html#Try)
and all other places
- **Imp** Access to the queued [email data in
pre/post-delivery](https://docs.halon.io/hsl/archive/5.4-stable/predelivery.html#f1)
as a `File` resource
- **Imp** `dns_query()` option for extended results including
[TTL and type-specific
fields](https://docs.halon.io/hsl/archive/5.4-stable/functions.html#dns_query)
- **Imp** Added [reply code
option](https://docs.halon.io/hsl/archive/5.4-stable/connect.html#Accept)
to all `Accept()` functions
- **Imp** `Auto-Submitted` header to bounces (rfc3834)
- **Imp** Saving custom views on queue pages
- **Imp** Custom metadata groupings and columns on queue pages
- **Imp** Number compare for metadata filters on queue pages
- **Imp** Partial and case sensitive matching of local parts on
queue pages
- **Imp** Real-time rates for counters on process statistics page
- **Imp** Added process stat counter for finished script hooks
- **Imp** Added multiple *recipientdomain* and *remotemx* option to
*if* statements in delivery configuration
- **Imp** Network matching in queue conditions for *sender-*,
*local-* and \*remoteip&
- **Imp** The API output is always normalized as UTF-8
- **Imp** The API supports comparing queue metadata as numbers
- **Imp** The `MIMEPart.setBody()` and `MailMessage.toString()`
limitation of 1 MiB is now removed
- **Imp** Extended error reporting from Sophos AV
- **Imp** Added *-R* to `hsh` to override *scripting.rootpath*
- **Imp** Added *–ffi* to `hsh` to override *scripting.ffi*
- **Imp** Added *–binary* flag to `hsh`
- **Imp** Added support for FFI in `hsl-lint`
- **Imp** Protobuf schemas are now part of the Linux package
- **Imp** Removed `httprd` from the Linux package
- **Imp** Added *queue load* command to `halonctl` to import
messages
- **Bug** Resolved issue with SMTPUTF8 and connection pooling
- **Bug** Wait for support processes like `rated` and `dlpd`
- **Bug** Log thread creation errors in *updateQueue*
- **Bug** Make the `domain_includes()` function
non-case-sensitive
- **Bug** Depend on `uuidd` runtime on Linux
- **Dep** The `Queue()` function (overrides the built-in logic)
may now exceed the transport’s max retry count
- **Dep** The `SetDSN()` function has been deprecated in favour
of the “dsn” option to Bounce() and Queue()
- **Dep** The `inet_includes()` function now return none on
errors
- **Dep** The `LDAPResult.next()` and `File.readline()` now
return false on end-of-data instead of none
- **Dep** The `MIMEPart.setBody()` limitation of 1 MiB is now
removed
- **Dep** The dns_query() function’s *ttl* result property has been
removed in favour of the *extended_result* option
- **Dep** All messages in the active queue now have at least one
*localip* (instead of showing an empty address)
## 5.3.5
Released 2020-06-10
- **Bug** Fixed a stability issue with the connection pool
## 5.3.4
Released 2020-05-26
- **Bug** Fixed issue with server connection concurrency limit
- **Bug** Fixed tags (comments) for some types of manual active
queue suspends
- **Bug** Fixed issue with the proxy script hook
## 5.3.2
Released 2020-03-10
- **Imp** Support more charsets by inclusion of latest GNU iconv
- **Imp** Changed log level of ldap_auth() function failures
- **Bug** Fixed issue with active queue policy with certain
groupings
- **Bug** Fixed issue with decoding of RFC2231 parameters values
- **Bug** Fixed regression with decoding of certain charsets
- **Bug** Fixed regression with Mellanox (mlx5en) network adapters
## 5.3.1
Released 2020-02-24
- **Imp** Added SMTP state information to delivery error logs
- **Imp** Added `port` option to
[dns_query()](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#dns_query)
function
- **Bug** Fixed issue with duplicate messages API call
- **Bug** Fixed “neq” condition (not equal) with “size” and
“retry_count” fields
- **Bug** Fixed issue with message size variable and per-recipient
end-of-DATA script
## 5.3
Released 2020-02-11
- **New** Queue features and API
- Connection
[pooling](https://docs.halon.io/manual/archive/5.3-stable/queue.html#delivery-settings)
(reuse)
- Fine-grained queue query
[API](https://docs.halon.io/manual/archive/5.3-stable/api.html)
(for
[list](https://docs.halon.io/manual/archive/5.3-stable/cli.html#queue-management),
update and distribution)
- Improved [queue
quota](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#queue_quota)
functionality
- Added delivery settings for [active queue
fields](https://docs.halon.io/manual/archive/5.3-stable/queue.html#delivery-settings)
- Added active [queue suspend and
policy](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#queue_suspend)
functions to more script hooks
- Added message size including modifications to
[pre-](https://docs.halon.io/hsl/archive/5.3-stable/postdelivery.html#message)
and post-delivery script
- New [on-disk queue
format](https://docs.halon.io/manual/archive/5.3-stable/queue.html#on-disk-format)
- **New** Command-line interface tool
[halonctl](https://docs.halon.io/manual/archive/5.3-stable/cli.html)
- **New** SMTP server scripting features
- Added timestamp, expiration and identity options to the
[signDKIM](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#MailMessage.signDKIM)
functions
- **Imp** Script language improvements
- New
[Iconv()](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#Iconv)
class for internationalization conversion
- Added
[array_every](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#array_every)
function to test all element against callback
- Re-implemented
[barrier](https://docs.halon.io/hsl/archive/5.3-stable/structures.html#barrier)
using shared
[memory](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#shared-memory)
functions
- Added Base64 encoding option to the MIME
set/[addHeader](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#MIME.addHeader)
functions
- **Imp** Certificate can be in [reloadable
configuration](https://docs.halon.io/manual/archive/5.3-stable/config_smtpd.html#confval-pki.private%5B%5D)
even if private key is in
[startup](https://docs.halon.io/manual/archive/5.3-stable/config_startup.html#confval-pki.private%5B%5D)
- **Imp** Added graceful draining to the background
[http()](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#http)
process *httprd*
- **Imp** Added idle timeout to [virtual
servers](https://docs.halon.io/manual/archive/5.3-stable/config_smtpd.html#confval-servers%5B%5D.timeout.idle)
- **Dep** [Important
changes](https://docs.halon.io/go/releasenotes53)
- Removed GetMailQueueMetrics() in favour for new
[queue_quota()](https://docs.halon.io/hsl/archive/5.3-stable/functions.html#queue_quota)
function
- Merged *smtpd* and *queued* processes which affects logging