Integrating Roundcube "Mark as Junk" Plugin with SpamAssassin (Bayes) Train on DirectAdmin

This tutorial provides a complete, production-ready guide to integrating the Mark as Junk plugin in Roundcube Webmail with SpamAssassin (sa-learn) on a DirectAdmin server. Using the spool directory driver (dir_learn) combined with a background poller script and per-user sudoers permissions ensures seamless processing without web interface timeout or permission errors.

Prerequisites: Root SSH access to your DirectAdmin server running CustomBuild and SpamAssassin.

Step 1: Create the Spam and Ham Directory

Create the spool folders where Roundcube will temporarily drop messages reported as junk or ham:

mkdir -p /var/spool/markasjunk/spam
mkdir -p /var/spool/markasjunk/ham
chown webapps:webapps /var/spool/markasjunk/spam /var/spool/markasjunk/ham
chmod 770 /var/spool/markasjunk/spam /var/spool/markasjunk/ham

Step 2: Automate Sudoers Rules for New Users (DirectAdmin Hook)

Set up a DirectAdmin post-user-creation script to automatically grant the webapps user passwordless sudo permissions for running sa-learn under newly created accounts:

mkdir -p /usr/local/directadmin/scripts/custom

cat > /usr/local/directadmin/scripts/custom/user_create_post.sh << 'SCRIPT'
#!/bin/sh
DAUSER="${username}"

if [ -z "$DAUSER" ]; then
    exit 0
fi

SUDOFILE="/etc/sudoers.d/sa-learn-${DAUSER}"

if [ ! -f "$SUDOFILE" ]; then
    cat > "$SUDOFILE" << EOF
webapps ALL=(${DAUSER}) NOPASSWD: /usr/bin/sa-learn --spam --dbpath=/home/${DAUSER}/.spamassassin/bayes *
webapps ALL=(${DAUSER}) NOPASSWD: /usr/bin/sa-learn --ham --dbpath=/home/${DAUSER}/.spamassassin/bayes *
EOF
    chmod 440 "$SUDOFILE"
    visudo -cf "$SUDOFILE" >/dev/null 2>&1 || rm -f "$SUDOFILE"
fi

exit 0;
SCRIPT

chmod 700 /usr/local/directadmin/scripts/custom/user_create_post.sh
chown diradmin:diradmin /usr/local/directadmin/scripts/custom/user_create_post.sh

Step 3: Provision Sudoers Rules for Existing Users

Create and execute a bootstrap script to generate the appropriate sudoers entries for all existing DirectAdmin users:

cat > /root/bootstrap-sa-learn-sudoers.sh << 'SCRIPT'
#!/bin/bash
while IFS=': ' read -r DOMAIN DAUSER; do
    DAUSER=$(echo "$DAUSER" | tr -d '[:space:]')
    [ -z "$DAUSER" ] && continue
    SUDOFILE="/etc/sudoers.d/sa-learn-${DAUSER}"
    if [ ! -f "$SUDOFILE" ]; then
        cat > "$SUDOFILE" << EOF
webapps ALL=(${DAUSER}) NOPASSWD: /usr/bin/sa-learn --spam --dbpath=/home/${DAUSER}/.spamassassin/bayes *
webapps ALL=(${DAUSER}) NOPASSWD: /usr/bin/sa-learn --ham --dbpath=/home/${DAUSER}/.spamassassin/bayes *
EOF
        chmod 440 "$SUDOFILE"
        visudo -cf "$SUDOFILE" >/dev/null 2>&1 && echo "Provisioned: $DAUSER" || { rm -f "$SUDOFILE"; echo "FAILED (invalid): $DAUSER"; }
    fi
done < /etc/virtual/domainowners
SCRIPT

chmod +x /root/bootstrap-sa-learn-sudoers.sh
/root/bootstrap-sa-learn-sudoers.sh

Verify that the rules were created successfully:

ls /etc/sudoers.d/ | grep sa-learn

Step 4: Configure the Mark as Junk Plugin Source File

Locate the active Roundcube webmail path and edit the plugin configuration to use the spool directory driver:

RCSOURCEPATH=$(find /var/www -maxdepth 2 -type d -iname "roundcubemail-*" 2>/dev/null | head -1)
echo "Live path: $RCSOURCEPATH"

cp "$RCSOURCEPATH/plugins/markasjunk/config.inc.php.dist" "$RCSOURCEPATH/plugins/markasjunk/config.inc.php"

sed -i \
  -e "s|\$config\['markasjunk_learning_driver'\] = null;|\$config['markasjunk_learning_driver'] = 'dir_learn';|" \
  -e "s|\$config\['markasjunk_spam_dir'\] = null;|\$config['markasjunk_spam_dir'] = '/var/spool/markasjunk/spam';|" \
  -e "s|\$config\['markasjunk_ham_dir'\] = null;|\$config['markasjunk_ham_dir'] = '/var/spool/markasjunk/ham';|" \
  -e "s|\$config\['markasjunk_filename'\] = null;|\$config['markasjunk_filename'] = '%d_%u';|" \
  "$RCSOURCEPATH/plugins/markasjunk/config.inc.php"

Confirm the plugin configuration parameters:

grep -E "learning_driver|spam_dir|ham_dir|filename" "$RCSOURCEPATH/plugins/markasjunk/config.inc.php"

Step 5: DirectAdmin CustomBuild Persistent Setup

Copy configuration and plugin files into the CustomBuild directory structure to ensure changes are retained across updates:

rm -rf /usr/local/directadmin/custombuild/custom/roundcube

mkdir -p /usr/local/directadmin/custombuild/custom/roundcube

cp "$RCSOURCEPATH/config/config.inc.php" /usr/local/directadmin/custombuild/custom/roundcube/config.inc.php

chmod 640 /usr/local/directadmin/custombuild/custom/roundcube/config.inc.php

RCCUSTOMCONFIG="/usr/local/directadmin/custombuild/custom/roundcube/config.inc.php"

sed -i -E \
  -e "s/['\"]managesieve['\"],[[:space:]]*//g" \
  -e "s/,[[:space:]]*['\"]managesieve['\"]//g" \
  "$RCCUSTOMCONFIG"

if ! grep -qE "['\"]markasjunk['\"]" "$RCCUSTOMCONFIG"; then
  sed -i -E "s|(\\\$config\['plugins'\]\s*=\s*\[[^]]*)\]|\1,'markasjunk']|" "$RCCUSTOMCONFIG"
fi

grep -E "plugins" "$RCCUSTOMCONFIG"

mkdir -p /usr/local/directadmin/custombuild/custom/roundcube/plugins

cp -r "$RCSOURCEPATH/plugins/markasjunk" /usr/local/directadmin/custombuild/custom/roundcube/plugins/

Step 6: Rebuild Roundcube and Restart Services

Recompile Roundcube via CustomBuild and restart Exim and SpamAssassin:

da build roundcube

systemctl restart spamd
da build exim_conf

Login to Roundcube, and set the INBOX.spam as Junk mailbox.
Settings > Preferences > Special Folders > "Junk = spam "

Step 7: Create the Background Poller Script and Cron Job

Set up a poller script that reads spooled messages, resolves the DirectAdmin user for each domain, trains SpamAssassin using sa-learn, and runs every 5 minutes via root cron:

cat > /usr/local/bin/process-markasjunk-spool.sh << 'SCRIPT'
#!/bin/bash
LOG=/var/log/rc-sa-learn.log

process_dir() {
    local DIR="$1"
    local TYPE="$2"
    for f in "$DIR"/*; do
        [ -f "$f" ] || continue
        FNAME=$(basename "$f")
        DOMAIN=$(echo "$FNAME" | cut -d_ -f1)
        DAUSER=$(grep -i "^${DOMAIN}:" /etc/virtual/domainowners 2>/dev/null | awk -F': ' '{print $2}' | tr -d '[:space:]')

        if [ -z "$DAUSER" ]; then
            echo "$(date): ERROR - could not resolve DA user for domain $DOMAIN (file: $FNAME)" >> "$LOG"
            mv "$f" "${DIR}/../failed_${FNAME}" 2>/dev/null
            continue
        fi

        BAYESPATH="/home/${DAUSER}/.spamassassin/bayes"
        sudo -u "$DAUSER" /usr/bin/sa-learn "--${TYPE}" --dbpath="$BAYESPATH" < "$f" >> "$LOG" 2>&1

        if [ $? -eq 0 ]; then
            rm -f "$f"
        else
            echo "$(date): WARNING - sa-learn failed for $FNAME" >> "$LOG"
        fi
    done
}

process_dir /var/spool/markasjunk/spam spam
process_dir /var/spool/markasjunk/ham ham
SCRIPT

chmod +x /usr/local/bin/process-markasjunk-spool.sh

(crontab -l 2>/dev/null; echo "*/5 * * * * /usr/local/bin/process-markasjunk-spool.sh") | crontab -

crontab -l

Step 8: Verification and Logs

  1. Log into the Roundcube Webmail interface.
  2. Select an email and click Mark as Junk (or As Not Junk).
  3. Verify that the message file lands in the spool folder and check the log output after the cron runs:
    ls -la /var/spool/markasjunk/spam/
    
    tail -20 /var/log/rc-sa-learn.log

Maintenance: Future Upgrade Workflow

When upgrading Roundcube via CustomBuild in the future, automatically resolve the active installation directory and compare your custom configuration files against the newly installed distribution files to detect any new configuration flags:

RCSOURCEPATH=$(find /var/www -maxdepth 2 -type d -iname "roundcubemail-*" 2>/dev/null | head -1)

# Check main configuration differences
diff /usr/local/directadmin/custombuild/custom/roundcube/config.inc.php "$RCSOURCEPATH/config/config.inc.php"

# Check plugin configuration differences
diff /usr/local/directadmin/custombuild/custom/roundcube/plugins/markasjunk/config.inc.php "$RCSOURCEPATH/plugins/markasjunk/config.inc.php.dist"

If any new $config[...] entries appear in the output, append or adapt them inside your custom configuration files under /usr/local/directadmin/custombuild/custom/roundcube/.

  • 22 Users Found This Useful
Was this answer helpful?

Related Articles

How to Configure DirectAdmin Spam Protection

Configuring proper server-wide spam protection is essential for maintaining server reputation,...