1.2. Queue configuration

The queue subsystem handles messages in queue. As described in the script language manual it is primaily configured using pre- and post-delivery hooks and the active queue policy that defined the virtual queues (using concurrency and rate).

1.2.1. Startup configuration

This is the non-reloadable part of the configuration. By default, queued loads it from /etc/halon/queued.yaml. It is described by, and can be validated with, the queued.schema.json.

It contains the environment setting documented in the programs section. The example configuration contains reasonable defaults, but you might need to configure the number of open files resource limit.

1.2.2. Running configuration

This the the reloadable part of the configuration. The default startup configuration loads it from /etc/halon/queued-app.yaml. It is described by, and can be validated with, the queued-app.schema.json.

It contains the bulk of the configuration data; most importantly the transport labels and delivery script.

It can be softly reloaded and deployed in a blue-green fashion using per-connection conditions from the integrated web admin, command line tools or control sockets.

The script is normally edited as separate files using the integrated package’s IDE (script editor) or the Visual Studio Code plugin. The actual YAML configuration is generated (or “packed”) by command line toolshct build command.

1.2.2.1. General directives

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. The hostname directive will be used as HELO name if defined.

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:

$sourceip = ["out", "out2", "outv6", "out2v6"];
if (GetMetaData()["spam"] == "yes")
  $sourceip = ["bulk"];
Try(["sourceip" => $sourceip]);
concurrency.total

The maximum total number of outbound SMTP connections that the queue process 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. Make sure that the number of files resource limit is a 3-4 times larger than this number. For 20000 connections, something like 70000 open files are needed in startup configuration. The default is 10000.

queued-app.yaml
concurrency:
  total: 20000
queued.yaml
environment:
  rlimit:
    nofile: 70000
pki.private[]

Array of private keys, possibly with X.509 certificates, for use with script 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.

pki:
  private:
    - id: selfsigned
      certificate:
        data: |-
          -----BEGIN CERTIFICATE-----
          ...
      privatekey:
        data: |-
          -----BEGIN PRIVATE KEY-----
          ...

Note

For privilege separation reasons, it’s normally recommended to define those in the startup configuration instead, as it is read before the privilege drop.

postmaster.name

The name used in the From header of system generated email such as delivery reports.

postmaster.address

The email address used in the From header of system generated email such as delivery reports.

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.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.

resolver.cache.ttl.max

Normalize the time-to-live values in the cache to have at most this value.

1.2.2.2. Script directives

The queue script can be used to change virtually any aspect of the queuing and delivery, and is probably the most central part of the queue configuration, together with the transportgroups[].

The script is normally edited as separate files using the integrated package’s IDE (script editor) or the Visual Studio Code plugin. The actual YAML configuration is generated (or “packed”) by command line toolshct build command.

For example, a script called src/hooks/queue/predelivery.hsl will result in:

scripting:
  hooks:
    predelivery: |
      ...
scripting.hooks.predelivery

The pre-delivery script executed as the first step of loading an email into the active queue.

scripting.hooks.postdelivery

The post-delivery script executed as the last step after a delivery attempt.

scripting.files[]

An array of virtual files used by the pre- and post-delivery script. It is most commonly used for imported script modules, include files, CSV lists and plain text files. Each file has an id and data property.

1.2.2.3. 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, as in the 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 have to “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”.

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. 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.port

The TCP port to use for the SMTP/LMTP connection. The default is 25.

transportgroups[].connection.sourceip.ipv4

An array with one or more IPv4 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[] IDs to use as local IP(s), or false to disable IPv6.

transportgroups[].session.protocol

Only needed if lmtp is to be used. The default is smtp.

transportgroups[].session.hostname

The HELO name to use. The default is to use the hostname of the addresses[] chosen as source IP, and to fall back to the system hostname.

transportgroups[].session.tls.mode

How to handle TLS for the connection. The default is not to use TLS (plain text). To do custom TLS verification per destination, use the pre-delivery script’s tls_X parameters. The script library contains an MTA-STS implementation.

  • optional: Use opportunistic, unverified TLS (fall back to plain text).

  • require: Require TLS, but don’t verify the peer.

  • dane: Verifiy the peer using DANE (DNSSEC). The most secure option.

  • dane_require: Require DANE. Only makes sense for specific destinations.

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.

transportgroups[].retry.count

The number of times to attempt re-delivery of an email via the defer queue. Determines the time in queue together with the transportgroups[].retry.intervals[].

transportgroups[].retry.intervals[]

An array of intervals (in seconds) for how long the email should stay in the defer queue before retry. If there are fewer intevals than transportgroups[].retry.count, the last interval will be used for consecutive defers. As with most other transport parameters, this behvariour can be overridden via pre- and post-delivery script. By setting notify to true on an interval, a delayed notification will be delivered to the sender at this time.

transportgroups:
- id: mxes
  retry:
    count: 30
    intervals:
      - interval: 60
      - interval: 900
      - interval: 3600
        notify: true
      - interval: 7200
      - interval: 10800
transportgroups[].dsn.transport

Enable bounces on delivery failures via the specified transport ID.

1.2.2.4. 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.

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: in
        connection:
          server: mail.example.com
          port: 25
        retry:
          count: 30
          intervals:
            - interval: 10
            - interval: 60
            - interval: 3600
concurrency:
  total: 10000
postmaster:
  address: [email protected]
  name: Postmaster
scripting:
  hooks:
    predelivery: ...

1.2.3. Queue policy configuration

The active queue policy is what defines the virtual queues. 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 startup configuration loads it from /etc/halon/queued-policy.yaml. It is described by, and can be validated with, the queued-policy.schema.json.

It is documented in the queue subsystem section.

1.2.4. Suspension configuration

The active queue suspensions can be used to pause traffic. 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 startup configuration loads it from /etc/halon/queued-suspend.yaml. It is described by, and can be validated with, the queued-suspend.schema.json.

It is documented in the queue subsystem section.