On shared hosting servers or VPS instances running cPanel and CloudLinux, establishing a protective ceiling for outgoing messages is common practice. Under WHM's Tweak Settings, administrators frequently configure Max hourly emails per domain to 500 messages per hour. This threshold prevents accidental mass mailing and limits spam damage from compromised accounts.
Problems arise when a specific client needs a higher allowance, such as 1,000 emails per hour, for legitimate transactional messages or store notifications.
Attempting to update the account or package directly through the WHM web interface produces an immediate rejection:
(XID psafa9) The value “1000” exceeds the server-wide limit of “500” set in Tweak Settings interface for “Max hourly emails per domain”.
This guide explains how this validation works under the hood, how to apply the desired threshold directly to the filesystem, and how to verify delivery behavior without introducing system instability.
1. Why error XID psafa9 occurs in WHM#
The perl management layer versus the c mail transport daemon#
The administrative layer of cPanel and WHM is largely implemented in Perl. When you submit package or account updates through the web interface, internal modules (such as Whostmgr::Packages) perform sanity checks.
The script inspects /var/cpanel/cpanel.config, reads the maxemailsperhour setting, and compares it against the submitted form input. If the requested value exceeds the global ceiling, the Perl script triggers a die() statement and displays the XID psafa9 error.
The design goal behind this interface safeguard is purely administrative: it stops reseller accounts from configuring excessive quotas that could saturate the server's outgoing queue.
However, the actual message transfer agent, Exim (written in C), does not consult the WHM user interface during message routing. When an outgoing message is handed off to the operating system, Exim's access control lists (ACLs) in /etc/exim.conf look for limits in a per-user metadata file.
2. Filesystem adjustments and common terminal traps#
To grant 1,000 hourly deliveries to a single account without altering global server policies, edit the user's plain-text metadata file directly:
/var/cpanel/users/username
The single-quote trap when using sed#
A frequent automation misstep in Bash is running an unquoted or single-quoted sed replacement:
# Flawed command example
TOTAL="1000" && sed -i 's/MAX_EMAIL_PER_HOUR=/MAX_EMAIL_PER_HOUR=$TOTAL/'g /var/cpanel/users/username
This fails for two reasons:
- Single quotes disable variable expansion: in POSIX shells, text inside single quotes (
' ') is evaluated literally. The shell passes$TOTALas text rather than expanding it to 1000. The file ends up withMAX_EMAIL_PER_HOUR=$TOTAL. When cPanel parses this value as an integer, the conversion results in zero or null, halting all outgoing mail for the account. - Missing line anchors: the regular expression
s/MAX_EMAIL_PER_HOUR=/.../matches only the key name. If the line already ended with 500, the resulting line becomesMAX_EMAIL_PER_HOUR=1000500.
Applying changes safely from the shell#
The proper method uses double quotes to allow variable interpolation and anchors the regular expression to the beginning of the line (^):
Case 1: The key already exists in the file
TOTAL="1000"
sed -i "s/^MAX_EMAIL_PER_HOUR=.*/MAX_EMAIL_PER_HOUR=$TOTAL/" /var/cpanel/users/username
Case 2: The key does not exist yet (account using system defaults)
If an account has never used a custom limit, the MAX_EMAIL_PER_HOUR directive is absent. The following defensive snippet checks for the key before deciding whether to update or append:
TOTAL="1000"
USER="username"
if ! grep -q "^MAX_EMAIL_PER_HOUR=" "/var/cpanel/users/$USER"; then
echo "MAX_EMAIL_PER_HOUR=$TOTAL" >> "/var/cpanel/users/$USER"
else
sed -i "s/^MAX_EMAIL_PER_HOUR=.*/MAX_EMAIL_PER_HOUR=$TOTAL/" "/var/cpanel/users/$USER"
fi
3. State synchronization across the cPanel ecosystem#
The role of updateuserdomains#
Simply saving the file on disk does not change runtime behavior immediately. To avoid disk I/O overhead from constant stat syscalls, cPanel daemons (cpanellogd, queueprocd, and exim) rely on indexed Berkeley DB (.db) files and internal caches.
To make the new quota active, rebuild these mapping databases:
/usr/local/cpanel/scripts/updateuserdomains
This script reads all files in /var/cpanel/users/, regenerates /etc/userdomains and /etc/trueuserdomains, updates cached directories in /var/cpanel/userdata/, and notifies background daemons to reload their routing tables.
Package synchronization warnings#
If an operator subsequently edits the account's hosting package through the WHM interface, the control panel rewrites /var/cpanel/users/username according to package defaults, reverting your custom value back to 500.
To avoid this:
- Switch the account to a custom package (or detach it from rigid shared plans).
- If you use automated provisioning scripts, add quota enforcement to your post-creation hooks.
4. Interactions with CloudLinux, LVE, and governors#
On CloudLinux servers, messages generated by PHP scripts (such as WordPress wp_mail or newsletter plugins) run inside an isolated Lightweight Virtual Environment (LVE).
Two factors require attention when accounts start sending higher message volumes:
Process concurrency limits during PHP sending#
If an application script spawns dozens of concurrent processes to dispatch messages simultaneously, the account can hit process thresholds (Entry Processes or Number of Processes).
Check whether an account is experiencing process rejections:
lveinfo --user username --period 1h
Examine the fEP (failed Entry Processes) and fNPROC (failed total processes) columns. Non-zero values mean the kernel is throttling PHP processes before messages ever reach Exim.
Database queries and MySQL governor#
Batch mailing scripts frequently run large database queries across contact lists. If these queries are unindexed or slow, CloudLinux's db_governor will throttle the account's queries to maintain server stability.
Check the account's governor status:
dbgovctl --status username
5. Real-time auditing and delivery validation#
Once the file is updated and caches are rebuilt, verify that Exim recognizes the adjusted limit.
Monitoring cPanel's hourly tracking directory#
cPanel logs sending volume in dedicated tracking files:
ls -lah /var/cpanel/email_send_limits/track/domain.com/
Each sent message writes to this directory, showing how many messages have left during the current one-hour rolling window.
Inspecting Exim transaction logs#
Follow live message transactions to confirm deliveries proceed without quota errors:
tail -f /var/log/exim_mainlog /var/log/exim_rejectlog | grep "domain.com"
If the account exceeds its hourly quota, Exim writes an explicit message indicating the domain has exceeded allowed limits.
Tracing system calls with strace#
To confirm which file Exim reads during address routing, trace system calls during address verification:
strace -f -e trace=open,openat,stat,read exim -bt [email protected] 2>&1 | grep "username"
Look for openat or stat calls referencing /var/cpanel/users/username. This confirms the MTA successfully read the configuration and loaded the adjusted limit into memory.
Monitoring Exim queue volume#
Check whether outgoing mail is accumulating in the spool:
# Print total message count in queue
exim -bpc
# List frozen or pending messages belonging to the user
exim -bp | grep "username"
6. Practical habits to protect server deliverability#
Raising per-domain limits is straightforward, but maintaining healthy server reputation requires continuous discipline:
- Watch bounce rates closely: accounts sending higher volumes must maintain clean recipient lists. Bounce rates exceeding 5% quickly degrade IP reputation with major mailbox providers.
- Maintain full email authentication: verify that SPF, DKIM, and DMARC records are valid and aligned before authorizing higher outgoing limits.
- Use third-party relay services for large volumes: if an account consistently requires thousands of messages per hour, route those messages through a dedicated external smarthost (such as SendGrid, Mailgun, or Amazon SES) to shield the primary server IP from delivery reputation risks.
Was this article helpful?
Leave a quick reaction to help prioritize future technical guides:
This post is licensed under CC BY-NC.



Comments
Join the discussion below.
0 comments