Guide
Mailx Alternative: Send Mail Without an MTA
mailx and mail(1) hand every message to a local MTA that most cloud servers no longer run, and Gmail rejects their plain-password SMTP auth. The Nylas CLI sends through your real account over OAuth with no daemon and no smtp= settings.
Written by Prem Keshari Senior SRE
Reviewed by Qasim Muhammad
Why does mailx fail without a local MTA?
mailx fails on most modern servers because it's a mail user agent, not a mail transfer agent: it composes the message, then hands delivery to /usr/sbin/sendmail, expecting a local MTA like Postfix or Exim to relay it. Default Ubuntu, Debian, and Amazon Linux cloud images ship no MTA, so the message lands in a dead.letter file instead of an inbox.
The POSIX.1-2017 specification says only that “the mailx utility provides a message sending and receiving facility” — how delivery happens is left to the system. Installing Postfix to fill that gap means maintaining a main.cf relay configuration, and cloud networks make even that unreliable: Google Compute Engine blocks all outbound traffic on port 25.
The usual workaround, Heirloom or s-nail -S smtp=smtp.gmail.com:587 settings with a plain password, died with provider auth changes. Google no longer supports “less secure apps” (personal Gmail dropped them in May 2022; Workspace followed starting January 2025, with final enforcement on May 1, 2025), and Microsoft retired Basic Auth in Exchange Online in October 2022. A password in ~/.mailrc now gets a 535 authentication error from both.
What is the best mailx alternative for sending mail?
The best mailx alternative for servers without an MTA is the nylas email send command. It sends through your real Gmail, Outlook, or IMAP account over HTTPS on port 443, so blocked port 25 and missing relay daemons stop mattering. Authentication is OAuth, refreshed automatically every 3,600 seconds.
The difference from SMTP-relay tools like msmtp or ssmtp is where credentials live. Those tools store an SMTP password and break when the provider rejects it; the CLI holds an API credential while the Nylas platform manages the OAuth grant for the mailbox. Setup drops from the 30–60 minutes a Postfix relay takes to about 2 minutes, and messages arrive from your actual address, so they inherit your account's SPF and DKIM alignment instead of a relay's reputation. Per RFC 5321, an SMTP relay chain adds a Received header and a failure point at every hop — an API call has exactly one.
How do I install the Nylas CLI on a server?
Install the Nylas CLI with one Homebrew command on macOS or Linux: brew install nylas/nylas-cli/nylas. The binary is static, downloads verify against SHA-256 checksums, and installation finishes in under 30 seconds on a typical connection. The getting started guide covers the other install methods, including a shell script for hosts without Homebrew.
Headless servers have no browser for an OAuth consent screen, so authenticate with the nylas auth config command and an API key instead. The key lands in your system keyring (not in a dotfile like ~/.mailrc where mailx kept SMTP passwords in plain text), but it stores credentials only — it doesn't pick a mailbox. Run nylas auth list to see every grant the key can reach, then select one: export NYLAS_GRANT_ID for cron and other unattended jobs, or run nylas auth switch with the account email on an interactive box. Minimal hosts without a keyring service also need NYLAS_DISABLE_KEYRING=true in the environment.
# Install (macOS and Linux)
brew install nylas/nylas-cli/nylas
# Authenticate without a browser
nylas auth config --api-key "$NYLAS_API_KEY"
# List the grants available under the API key
nylas auth list
# Select the sending mailbox: env var for cron and scripts...
export NYLAS_GRANT_ID="<grant-id>"
# ...or a stored default on an interactive host
nylas auth switch ops@example.comHow do I map mailx flags to nylas email send?
Every common mailx flag has a direct nylas email send equivalent: the recipient operand becomes --to, -s becomes --subject, -c becomes --cc, and -b becomes --bcc. POSIX requires only -s on all systems; the rest come from the bsd-mailx and s-nail variants. The table below covers all 9 mappings.
| mailx usage | Purpose | nylas email send equivalent |
|---|---|---|
mailx ops@example.com | Recipient (operand) | --to ops@example.com |
-s "Subject" | Subject line | --subject "Subject" |
-c lead@example.com | CC recipient | --cc lead@example.com |
-b audit@example.com | BCC recipient | --bcc audit@example.com |
echo body | mailx ... | Message body via stdin | --body "$(command)" |
-r me@example.com | Sender address | Set by the authenticated account; no flag |
-S smtp=... -S smtp-auth=login | SMTP relay + password | Not needed; HTTPS with OAuth |
-v | Show delivery dialog | --verbose |
| No equivalent | Scheduling, open tracking | --schedule "tomorrow 9am", --track-opens |
Here's a typical migration side by side. The mailx version needs a working relay behind it; the CLI version needs nothing but the binary and a one-time auth. The --yes flag skips the interactive confirmation prompt, which matters once this runs unattended.
# Before: mailx (requires a configured MTA or smtp= settings)
echo "Build 412 deployed to production" | mailx \
-s "Deploy complete" \
-c lead@example.com \
ops@example.com
# After: Nylas CLI (no MTA, OAuth through your real account)
nylas email send \
--to ops@example.com \
--cc lead@example.com \
--subject "Deploy complete" \
--body "Build 412 deployed to production" \
--yesHow do I replace mailx in shell scripts and cron jobs?
Replace piped mailx calls in scripts by moving the piped output into the --body flag with command substitution. mailx reads the message body from stdin; nylas email send doesn't, so a script that pipes df -h into the new command sends an empty body. --body "$(df -h)" is the fix promised in the TL;DR.
The pattern below converts the classic disk-report one-liner. Command substitution captures the full report (about 8 lines on a typical host) and delivery completes in roughly 3 seconds over HTTPS. Add --yes so the command never blocks waiting for a confirmation that cron can't answer.
#!/bin/bash
# Old habit: df -h | mailx -s "Disk usage" ops@example.com
# New version: capture output, pass it with --body
nylas email send \
--to ops@example.com \
--subject "Disk usage on $(hostname)" \
--body "$(df -h)" \
--yesIn a crontab, use the full path to the binary because cron's default PATH includes only /usr/bin and /bin. Redirect stderr to a log so failures are visible — cron discards output otherwise. The cron job email guide covers PATH, HOME, and debugging in depth.
# Daily uptime report at 7:00 AM, no MTA on the box
0 7 * * * /home/linuxbrew/.linuxbrew/bin/nylas email send \
--to ops@example.com \
--subject "Morning status" \
--body "$(uptime)" \
--yes 2>> /var/log/cli-email.logNext steps
You've replaced mailx with a single OAuth-backed command that survives provider auth changes and runs on hosts with no MTA installed. Migrate one script with the flag table above, then expand from there.
- Send email from a bash script — exit codes, retries, and variable handling for the scripts you just migrated
- Cron job email without Postfix — PATH, HOME, and logging fixes for unattended sends
- Send email from terminal — the full send reference including HTML bodies and scheduling
- EmailEngine vs Nylas — how a self-hosted IMAP bridge compares if you'd rather run your own mail infrastructure
- Twilio vs Nylas — transactional sending platforms compared with account-based sending
nylas email sendcommand reference — every flag, including--scheduleand--track-opens- Full command reference — all CLI commands for email, calendar, contacts, and auth
- POSIX mailx specification — the standard that defines mailx send mode and the
-sflag - RFC 5321 (SMTP) — the relay protocol mailx depends on and the CLI skips
- Google Cloud: sending mail from an instance — why port 25 is blocked on GCE and what Google recommends instead