Decoupled architecture of Imunify email: reverse engineering thresholds and quarantine monitoring
Back to blog

Decoupled architecture of Imunify email: reverse engineering thresholds and quarantine monitoring

10/17/2026 · 4 min · Cybersecurity

On cPanel and CloudLinux servers running Imunify Email (IE), official documentation frequently mentions deprecated commands or abstracts away what the binaries actually do under the hood. A common issue arises when attempting to adjust spam score thresholds: messages with high spam scores (above 0.80) continue reaching recipient inboxes with a No Action verdict instead of being isolated in quarantine.

When following standard guides to modify these parameters, administrators frequently encounter non-existent commands and configuration layouts that differ entirely from published manuals.

In this article, we break down Imunify Email's microservice design, trace runtime signals from the Go binary using system calls, explain the conditions that cause spam bypasses, and set up an automated monitoring script to prevent storage limits from disabling protection.


1. Why traditional commands fail in ie-cli#

Many outdated guides recommend using ie-cli config show --global to inspect or alter scoring thresholds. On modern production systems, running this command results in an immediate syntax error:

[root@server ~]# ie-cli config show
Error: unknown command "config" for "ie-cli"

Go runtime clues in system calls#

Running strace against the binary to identify which configuration files were being opened revealed an important behavioral detail:

[root@server ~]# strace -e openat ie-cli filter-settings 2>&1 | grep -v "ENOENT"
openat(AT_FDCWD, "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", O_RDONLY) = 3
--- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=1907699, si_uid=0} ---
{
    "allow_windows_executable_attachments": true
}
+++ exited with 0 +++

The interception of the SIGURG signal delivered via SI_TKILL confirms that ie-cli and its companion daemons are compiled in Go (Golang). Since Go 1.14, the runtime uses asynchronous SIGURG signals for non-cooperative goroutine preemption. In addition, accessing /sys/kernel/mm/transparent_hugepage reflects the internal memory allocator sizing pages dynamically.

Microservice process structure#

Rather than operating as a single daemon, Imunify Email runs three decoupled components located in /etc/imunifyemail/ (note the unhyphenated directory name):

ps aux | grep -E "ie-|rspamd"

The output reveals the active daemons:

_imunify+    2962  0.0  0.0  /usr/bin/ie-dec-node run --config /etc/imunifyemail/dec-node.yaml --env-file /etc/imunifyemail/dec-node.env
_imunify+    2965  0.0  0.0  /usr/bin/ie-quarantine run --config /etc/imunifyemail/quarantine.yaml --env-file /etc/imunifyemail/quarantine.env
_rspamd      4116  0.0  0.0  rspamd: controller process (/var/lib/rspamd/rspamd-ctl.sock mode=0660 group=_imunify)

Each process handles a dedicated stage in the mail flow:

  1. ie-dec-node: decision node. Acts as asynchronous middleware, receiving analytical scores and determining the fate of the message.
  2. ie-quarantine: quarantine storage manager. Manages retention Maildirs and updates index databases.
  3. rspamd: heuristic scoring engine. Runs alongside the Imunify daemons and communicates over dedicated UNIX sockets with _imunify group permissions.

2. Dec-node.yaml structure and decision flow#

The configuration file /etc/imunifyemail/dec-node.yaml defines listener addresses and disk safety thresholds:

server:
  address:    :11339
  insecure:   false
  readTimeout: 10s
  writeTimeout: 10s
  socketAddress: /var/run/imunifyemail/dec-node.sock

logger:
  level: info
  outputPaths:
    - stdout

cleaner:
  rootDir: /var/imunifyemail/dec-node/storage/data
  hardQuotaGb: 1
  softQuotaGb: 0.9
  interval: 500s

saver:
  rootDir: /var/imunifyemail/dec-node/storage/data
  indexFileName: /var/imunifyemail/dec-node/storage/index.db

Data flow during message processing#

When Exim receives an email, the message travels through the following sequence:

[Exim MTA]
    │  (Passes message payload via filter transport)
    ▼
[Rspamd] ──(Scores message using statistical and heuristic rules)
    │
    ▼  Emits score symbols over socket /var/run/imunifyemail/dec-node.sock
[ie-dec-node] ──(Checks local index.db and environment settings)
    │
    ├─ If Score >= Threshold ──> Writes message to quarantine storage and updates index.db
    │                             ▲
    │                             │ (Handled by ie-quarantine)
    └─ If Score < Threshold  ───> Returns "No Action" so Exim completes delivery to inbox

Because ie-cli filter-settings only exposes boolean flags (such as blocking Windows executables), score thresholds do not exist in plain-text configuration files. Instead, they are evaluated through the environment file loaded at startup (/etc/imunifyemail/dec-node.env) and the embedded SQLite database (/var/imunifyemail/dec-node/storage/index.db).


3. Why high-scoring messages receive a "no action" verdict#

When suspected spam messages bypass quarantine, three primary conditions should be investigated from the command line.

1. Score scale mismatch between Rspamd and Imunify#

Rspamd operates natively with an open-ended weight scale (typically between 5.0 and 15.0 for spam). Imunify Email applies a statistical normalization formula to convert that raw score into a decimal probability between 0.00 and 1.00.

To verify whether raw Rspamd action rules are interfering:

rspamadm configdump actions

Conflicting thresholds in Rspamd can alter the metric values delivered to /var/run/imunifyemail/dec-node.sock.

2. Cleaner quota saturation (fail-open safety mode)#

In dec-node.yaml, the cleaner module defines strict volume limits: hardQuotaGb: 1 and softQuotaGb: 0.9. If global quarantine storage or an individual mailbox reaches these limits, Imunify Email enters a passive Fail Open state.

To avoid SQLite database corruption or outbound disk stalls, the daemon begins returning No Action on subsequent incoming messages, allowing them through without quarantine.

To check storage usage across mailboxes:

ie-cli accounts list

If the UsedBytes column matches or approaches LimitBytes, automated quarantine retention has been temporarily suspended to protect disk health.

3. Structural bypasses and whitelists#

Messages lacking mandatory RFC header fields (such as system bounces without a valid Message-ID or timestamp) or senders matching local bypass rules will skip quarantine entirely.

To query active bypass settings in the SQLite database:

sqlite3 /var/imunifyemail/dec-node/storage/index.db "SELECT * FROM settings WHERE key LIKE '%whitelist%' OR key LIKE '%bypass%';"

4. Operational commands and automated monitoring#

Once the system layout is clear, storage capacities can be adjusted and monitored proactively to prevent unwanted bypasses.

Adjusting global quarantine limits#

To inspect current default storage limits across managed mailboxes:

ie-cli quarantine-defaults list

To increase storage capacity per mailbox (for example, raising it to 200 MB) and avoid the Fail Open trigger:

ie-cli quarantine-defaults set --storage-capacity 200

Automated quarantine storage monitoring script#

To avoid relying on manual checks, a lightweight Bash script can inspect ie-cli accounts list, calculate mailbox usage percentages, and send warning events to Syslog whenever a mailbox exceeds 80% capacity.

Save the script to /usr/local/bin/ie_quarantine_monitor.sh:

#!/bin/bash
# Imunify Email quarantine storage monitor

ALERT_PERCENT=80
LOG_DIR="/var/log/imunifyemail"
LOG_FILE="${LOG_DIR}/quarantine_monitor.log"
BINARY="/usr/bin/ie-cli"

if [ ! -d "$LOG_DIR" ]; then
    mkdir -p "$LOG_DIR"
    chmod 750 "$LOG_DIR"
fi

if [ ! -x "$BINARY" ]; then
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] ie-cli binary not found or not executable." >> "$LOG_FILE"
    exit 1
fi

$BINARY accounts list | grep -E '^[a-zA-Z0-9]' | while read -r NAME USED LIMIT RELEASE FILT; do
    if [[ "$USED" =~ ^[0-9]+$ ]] && [[ "$LIMIT" =~ ^[0-9]+$ ]] && [ "$LIMIT" -gt 0 ]; then
        PERCENT=$(( USED * 100 / LIMIT ))

        if [ "$PERCENT" -ge "$ALERT_PERCENT" ]; then
            TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
            ALERT_MSG="[QUOTA_ALERT] Account: ${NAME} reached ${PERCENT}% usage (${USED} of ${LIMIT} bytes) in quarantine."

            echo "[${TIMESTAMP}] ${ALERT_MSG}" >> "$LOG_FILE"
            logger -p mail.warn -t IMUNIFY_EMAIL_MONITOR "${ALERT_MSG}"
        fi
    fi
done

exit 0

Make the script executable and schedule it through cron every 10 minutes:

chmod +x /usr/local/bin/ie_quarantine_monitor.sh
echo "*/10 * * * * root /bin/bash /usr/local/bin/ie_quarantine_monitor.sh >/dev/null 2>&1" > /etc/cron.d/imunify-quarantine-monitor
systemctl restart crond

Live log inspection for Imunify daemons#

To watch decision events as new mail arrives:

# Monitor verdicts generated by the decision node
journalctl -u ie-dec-node -f --since "1 hour ago" | grep -iE "score|action|quarantine"

# Monitor quarantine storage operations
journalctl -u ie-quarantine -f --since "1 hour ago"

Preventive maintenance and ongoing quarantine monitoring#

Understanding the separation between the decision node (ie-dec-node), quarantine storage (ie-quarantine), and the scoring engine (rspamd) eliminates confusion when configuring newer Imunify Email releases.

For long-term reliability:

  1. Watch mailbox storage ceilings: remember that protection defaults to Fail Open when individual account limits or the global cleaner threshold in dec-node.yaml are exceeded.
  2. Use journalctl for troubleshooting: runtime diagnostic events are logged directly to Systemd units rather than standard flat files.
  3. Automate quota alerts: routing storage warnings to Syslog helps catch full quarantine mailboxes before legitimate spam filters are bypassed.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments