Sending Email from the Linux Command Line: Safe Local MTA, SMTP, and API Paths

This article was published in 2014 under the title “Checking Mail with mail on Linux,” but its body mixed local mailbox reading, a test send, and multi-user terminal messaging. This maintained edition addresses one narrower question: how to send one authorized email safely and auditably from the Linux command line. Local mailbox reading, IMAP/POP3 retrieval, and write terminal messaging are separate topics; their commands are not interchangeable.

The boundary comes first: use this guide only with sending accounts, domains, hosts, and recipients that you own or are authorized to use. It does not show how to create an open relay, forge senders, evade anti-abuse controls, send unsolicited bulk mail, or hide origin. Never disable TLS certificate checks, place a password in an argument or URL, or point a production queue at unconsenting recipients just to “make it work.”

The complete visible body from source_export is preserved at the end. Old email addresses and message identifiers in the archive are narrowly redacted; old HTTP links remain provenance, not current recommendations.

Choose the right submission path first

PathSuitable useTrust boundaryNot suitable for
Local MTA sendmail-compatible interfaceAdministrator-managed servers, system notifications, an existing queue and bounce processThe application hands the message to a local Postfix, Exim, Sendmail, or compatible implementation; that MTA delivers itImprovised public SMTP on a personal host or bypassing organizational policy
Authenticated SMTP submissionSingle-user scripts and desktop/server clients where the provider documents a submission hostThe client validates TLS and authenticates to a provider; the provider performs onward deliveryDirect delivery to arbitrary recipient MX hosts on port 25 or shared long-lived passwords
Provider mail APIApplications that need structured responses, idempotency keys, templates, or event statusThe application holds a least-scope API credential and follows provider data and retry rulesInvented generic endpoints, tokens in command lines, or quota evasion

mail/mailx names a family of mail user agents whose implementations and options differ by distribution. sendmail is often only a compatibility interface and does not identify the MTA behind it. Confirm the system owner, package source, and responsible operator before inferring configuration from a binary name.

Record provider facts before changing anything

Record the following from the organization administrator or the provider's current official documentation, with a review date:

  • the submission hostname and the exact 587/STARTTLS or 465/implicit-TLS combination;
  • the supported authentication mechanism: OAuth 2.0, a short-lived token, an app password, or another organization-approved method;
  • verified From: identities, allowed envelope-from/Return-Path domains, and who signs DKIM;
  • message-size, rate, concurrency, daily-quota, recipient-count, and retry limits;
  • where bounces, complaints, suppression lists, logs, and content are kept, and for how long;
  • API region, version, scopes, idempotency, and revocation procedure if an API is used.

An ordinary account password may not be accepted for SMTP AUTH, and enabling multi-factor authentication does not imply that the sign-in password can be reused. OAuth and app-password availability depends on provider and organization policy. Stop if the official parameters, sending authority, or a controlled test inbox are unavailable.

Distinguish the envelope from message headers

The SMTP envelope and the headers visible to a reader are separate layers. RFC 5321 defines the transport envelope and RFC 5322 defines message format; neither is automatically equal to the other.

FieldPurposeSecurity requirement
envelope MAIL FROMBounce address; its domain may participate in SPF/DMARCUse a provider-approved identity with a working bounce process; do not improvise it in scripts
envelope RCPT TOThe actual delivery destinationAccept only a fixed allowlist or validated business data; reject newline injection
From:Author identity shown to the reader and source of the DMARC Author DomainUse an authorized identity aligned with the provider's SPF/DKIM design
To: / Cc:Visible display recipientsThey need not equal envelope recipients; never expose other people's addresses
Bcc:Delivered through the envelope but absent from the final headerVerify that the client removes it; use a compliant list system rather than a large Bcc for bulk mail
Message-ID: / Date:Deduplication, threading, and diagnosisLet a trusted client/MTA generate them and redact them before sharing logs

Never concatenate untrusted text into headers; CR/LF characters can create extra headers or recipients. Every example here uses the RFC-reserved .invalid domain and therefore cannot deliver as written.

Path one: an existing local MTA’s sendmail interface

Begin with read-only inventory:

command -v sendmail
command -v msmtp
msmtp --version

If /usr/sbin/sendmail exists, determine which maintained package owns it, who administers it, which interfaces it listens on, whether authentication and relay restrictions are correct, and who owns queues and bounces. An application can commonly submit a reviewed message to the local queue through a compatible call like this:

/usr/sbin/sendmail -t -oi < "$HOME/mail-test.eml"

-t commonly extracts recipients from message headers, while -oi prevents a body line containing only a dot from ending input early; verify the final semantics in the installed implementation's manual. A successful exit means the local MTA accepted the message, not that a remote server delivered it. Do not expose a listener to the Internet, create an unauthenticated relay, broaden mynetworks/ACLs, or use -f to impersonate a domain. If there is no administrator-approved queue, bounce, and logging process, use the organization's submission service or API.

Path two: authenticated SMTP submission

RFC 6409 separates message submission from server-to-server relay. Ordinary clients should use the provider's submission service, not attempt direct delivery to a recipient MX on port 25. RFC 8314 recognizes two secure deployments:

ModeCommon portWhen TLS beginsWhat must be verified
STARTTLS submission587SMTP starts first and upgrades through STARTTLS; stop if the upgrade failsHostname, certificate chain, validity, successful STARTTLS, and only then authentication
Implicit-TLS submission465TLS begins immediately after TCP connectionHostname, certificate chain, and validity before authentication

Ports are conventions; the provider's official configuration is authoritative. A bad system clock or CA store can also break validation. Repair the cause—never use trust-all, tls_certcheck off, ignored hostnames, or an unreviewed pinned certificate fingerprint.

You can probe TLS with the provider-documented hostname without sending credentials or a message. First confirm that the local OpenSSL version supports these options:

openssl s_client -starttls smtp -connect smtp.example.invalid:587 -servername smtp.example.invalid -verify_hostname smtp.example.invalid -verify_return_error -brief </dev/null
openssl s_client -connect smtp.example.invalid:465 -servername smtp.example.invalid -verify_hostname smtp.example.invalid -verify_return_error -brief </dev/null

A successful probe proves only that the network/TLS path worked at that moment; it does not prove account authentication, sender authorization, or final delivery. Stop on a hostname mismatch, unknown CA, downgrade or captive-proxy page, missing STARTTLS on 587, or an unexpected host.

Keep non-secret settings in an isolated msmtp configuration

msmtp is a maintained SMTP client with a sendmail-compatible interface. Install it from a supported distribution package and check its version, then create an isolated file that cannot overwrite a default account:

ConfigDir="$HOME/.config/msmtp"
ConfigFile="$ConfigDir/config-controlled"
test ! -L "$ConfigDir" || { echo "Stop: $ConfigDir is a symlink"; exit 1; }
test ! -e "$ConfigFile" && test ! -L "$ConfigFile" || { echo "Stop: $ConfigFile already exists"; exit 1; }
umask 077
install -d -m 700 -- "$ConfigDir"
install -m 600 /dev/null "$ConfigFile"

Confirm that both directory and file belong to the current user. Enter this 587/STARTTLS template in an editor; every value remains a non-deliverable placeholder:

account controlled
host smtp.example.invalid
port 587
auth on
user sender@example.invalid
from sender@example.invalid
allow_from_override off
tls on
tls_starttls on
tls_trust_file system
set_from_header on
set_date_header auto
set_msgid_header auto
remove_bcc_headers on

This template targets the official msmtp 1.8.34 manual. If the official manual for the installed version does not list set_msgid_header, do not guess or ignore the error: upgrade through a supported package, or have a reviewed MUA/provider generate Message-ID:.

If and only if the provider requires 465 with implicit TLS, change these two lines:

port 465
tls_starttls off

In msmtp, tls_starttls off together with tls on means that the connection is inside TLS from its beginning; it does not disable encryption. Keep default certificate verification and never add tls_certcheck off.

The configuration deliberately has no password. For an interactive test, let the client use a supported system keyring or a secure TTY prompt. For unattended operation, follow the provider and official msmtp documentation to integrate an audited OAuth-token helper, keyring, or passwordeval. msmtp may also look for a legacy ~/.netrc; confirm that the test account has no unintended match, without printing that file. The helper must have least privilege and must not expose secrets through stdout beyond the expected value, logs, environment snapshots, or temporary files. Never place a password/token in argv, a URL, shell history, a repository, a .netrc example, or a screenshot.

Expand the effective configuration without sending. --pretend prints asterisks instead of the password, but its output can still contain identities, hosts, and paths, so redact it before sharing:

msmtp --file="$HOME/.config/msmtp/config-controlled" --account=controlled --pretend

Do not use --debug with a real account: the official manual warns that a complete conversation can expose password material in an easily decodable form.

Build a minimal message and use a controlled sink first

Save the following as $HOME/mail-test.eml with mode 600. Keep the .invalid values initially and omit attachments, personal data, production templates, real Bcc addresses, and tracking pixels:

From: sender@example.invalid
To: controlled-recipient@example.invalid
Subject: controlled submission test
Auto-Submitted: auto-generated

This is an authorized delivery test. No reply is required.

For stage one, point an isolated test account at an organization-approved development SMTP sink or sandbox. The sink must bind only to loopback or a controlled test network, must not relay to the public Internet, and should retain data briefly. Confirm that it captures exactly one message with correct headers and no Bcc or secret, then clear its test data. --pretend checks only the client configuration it would use; it does not replace a sink test.

Only in stage two replace the host, sender, and sole recipient with values you control and the provider has approved. Review the diff line by line, then send once:

msmtp --file="$HOME/.config/msmtp/config-controlled" --account=controlled --read-recipients < "$HOME/mail-test.eml"

This configuration locks the envelope-from and reads envelope recipients from message headers. Do not pass unvalidated addresses as positional arguments, and do not make the first send from a loop, scheduler, or retry worker. An interactive credential prompt must come from the reviewed local client, not a web popup or unknown script.

Path three: a provider API

An API can suit applications that need structured submission IDs, idempotency, or event callbacks, but it is not a route around mail policy. Use only the provider's current official HTTPS endpoint, SDK, and certificate validation. Keep the token in OS secret storage or a read-only runtime mount, scope it to one sending service/domain and the necessary actions, and define rotation and expiry.

Do not copy a “generic curl” example and put a Bearer token in argv or the environment. Request bodies, failure responses, proxies, and APM can also log recipients and message content. Begin in the provider's sandbox or validation mode; keep the first production request to one controlled recipient and one unique idempotency key. Retry temporary errors only with the provider's documented bounded backoff rules; never blindly replay permanent rejection, authorization, or quota errors.

SPF, DKIM, and DMARC belong to the domain owner

  • SPF authorizes infrastructure to send for the envelope MAIL FROM/HELO domain; a command-line client cannot “pass SPF” by adding a header.
  • DKIM is added by a trusted MTA/provider using a domain key; private keys do not belong in ordinary scripts or workstations.
  • DMARC uses the visible From: Author Domain and requires alignment with a passing SPF or DKIM identifier. Current RFC 9989 obsoletes RFC 7489. A pass proves authorized domain use, not safe content or guaranteed inbox placement.

Only the domain owner or an explicit delegate may modify DNS records, selectors, reporting addresses, and enforcement policy. Inventory every legitimate mail stream, complete provider verification, and observe reports before staged policy changes; do not copy someone else's TXT record or jump directly to a rejection policy. DMARC reports and bounces can contain addresses, IPs, headers, or message excerpts, so restrict access and retention.

Delivery evidence, bounces, and privacy

EvidenceWhat it provesWhat it does not prove
Client exit code/submission IDThe client or submission/API accepted the requestFinal recipient server acceptance or inbox placement
Local queue IDThe local MTA queued itEventual queue success or authentication pass
Remote SMTP 250/provider delivered eventA remote stage accepted the messageReading, trustworthy content, or no later bounce
Received and Authentication-Results in inbox sourceThe test message's real path and SPF/DKIM/DMARC resultsIdentical behavior at every provider or for later messages
DSN/bounce/complaint eventA particular failure or user signalPermission to retry forever or keep contacting the address

Record only the test time, configuration version, submission/queue ID, redacted status code, and final outcome. Do not retain full content, OAuth tokens, AUTH transcripts, complete recipient lists, or unredacted raw headers. Someone must own the bounce address; hard bounces, complaints, unsubscribes, and explicit refusals belong in suppression, not automatic resubmission.

Provider 4xx responses usually denote a temporary condition and 5xx a permanent rejection, but follow provider documentation and enhanced status codes. Retries need bounded attempts/time, jitter, and deduplication. If acceptance of the original request is uncertain, query by submission ID before risking a duplicate.

Common failures and stop conditions

SymptomEvidence to inspect firstNext step or stop condition
TLS hostname/chain/validity failureHostname, system clock, CA updates, proxyRepair the source of truth; never disable validation
No STARTTLS on 587Official port, EHLO capabilities, interceptionStop authentication; never send a secret over plaintext
535/authentication failureAuthentication type, account policy, token scope/expiryDo not guess repeatedly; revoke exposed credentials and reissue officially
550 sender or recipient rejectedVerified identity, envelope/header, enhanced statusDo not forge From: or rotate domains to evade policy
Local command succeeds but mail is absentExact queue ID, MTA log, bounce routeHave the administrator inspect that record; do not flush the whole queue
SPF/DKIM/DMARC failureInbox source, DNS, signing domain, alignmentDomain/provider owner fixes it; do not forge authentication headers locally
Quota exceeded or many 4xx responsesProvider limits, queue depth, duplicate keysPause producers; do not raise concurrency to evade limits
Recipient/content origin is unclearAuthority, consent, data source, retention basisDo not send until an owner confirms it

Never use tls_certcheck off, --insecure/trust-all, plaintext AUTH, password arguments, an open relay, sender spoofing, purchased/scraped lists, unsolicited bulk mail, or disabled provider anti-abuse, unsubscribe, or suppression controls.

Rollback, revocation, and offboarding

  1. Before the first live test, retain a redacted configuration diff, owner, test address, rate ceiling, and rollback window.
  2. On an anomaly, stop the scheduler/queue producer from creating new mail first; do not indiscriminately delete other applications' queues.
  3. Act only on exact submission/queue IDs for this test, and determine whether already accepted mail can still arrive.
  4. Restore the prior verified configuration, revoke or rotate the dedicated token/app password, and remove the temporary message and sink data.
  5. When retiring an account, remove keyring/secret mounts and service permissions, revoke API/OAuth credentials, and check whether other streams still use the DKIM selector, DNS authorization, webhook, or bounce address before changing them.
  6. Finally verify that no orphan scheduler, public listener, unhandled bounce, queued backlog, or long-lived sensitive log remains.

Acceptance checklist for one controlled email

  • The sending account, domain, host, and sole test recipient are explicitly authorized.
  • Current official provider documentation supplied the host, port, TLS, authentication, sender identity, quota, and bounce process.
  • Local MTA, authenticated submission, and API paths are distinguished; there is no arbitrary-MX direct delivery or open relay.
  • TLS hostname and certificate validation succeeds; credentials are used only after encryption.
  • No password/token is present in argv, URLs, repositories, messages, logs, screenshots, or history.
  • envelope-from, From:, recipients, and SPF/DKIM/DMARC ownership are aligned.
  • A local/provider sink passed; production sends exactly one message to a controlled address.
  • Redacted submission ID, remote result, and authentication-header evidence is retained, with “accepted” distinguished from inbox/reading.
  • Rate, retry, bounce, complaint, unsubscribe, suppression, and privacy retention have owners.
  • Stop, exact rollback, credential revocation, and offboarding have been tested.

References

Historical source archive

The following is the complete inert archive of the visible 2014 body in source_export. Six narrow replacements avoid re-exposing identifiable old email and per-message identifiers: one mailbox-path username, one mailbox-list email, one local identity, two email addresses, and one Message-ID. General tutorial mentions of frank/renee, historical domains, and two old HTTP source links remain unchanged; the links are provenance only, not current service, download, or configuration advice. The source had no trailing whitespace, and nothing else was changed. Do not execute any command, address, or practice inside the fence.


Table of Contents

Toggle

- [Linux下mail使用技巧](https://blog.lazying.art/en/html/computer_internet/unix_linux/command_shell_software/1143/linux%e4%b8%8b%e9%82%ae%e4%bb%b6mail%e6%9f%a5%e6%94%b6.html/#Linux%E4%B8%8Bmail%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7)

# Linux下mail使用技巧

登录LINUX系统后,经常会看到”you have mail”,却苦于不知道如何查看,相信菜鸟们都遇到过,偶在网上用“linux mail”找了很久,但大都是介绍[mail服务器](http://earnfs.sinaapp.com/html/1141.htm)的,黄天总算没负有心人,在洪恩在找到一篇介绍基础的文章,不敢独享。

系统提供了用户之间通信的邮件系统,当用户打开终端注册登录时发现系统给出如下信息:
 you have mail.

这时用户可通过键入mail命令读取信件:

$ mail

mail程序将逐个显示用户的信件,并依照时间顺序,显示最新的信件。每显示一段信件,mail都询问用户是否要对该信件作些处理。若用户回答d,则表示 删除信件;若仅按回车键,表示对信件不作任何改动(信件仍旧保存,下次还可读这一信件);若回答p,则要求重复显示信件;s filename表示要把信件存入所命名的文件;若回答q,表示要从mail退出。

我们在本章的第一个例子中演示了如何写一封信,作为练习,你可送信件给自己,然后键入mail读取自己发的信件,看看会有什么效果。(发信给自己是一种设置备忘录的方法)。

$mail frank 给自己写信

subject: test

This is a mail test

CRL-d

EOT

$

$mail 查看信件

“/var/spool/mail/[historical user redacted]:”1 message 1 new

>N[historical email redacted]Thu Mar 25 11:00 13/403 “test”

&

Message 1:

From frank Thu Mar 25 11:00:25 1999/3/25

Received: ([historical local identity redacted])

by xteam.xteamlinux.com(8.8.4/8.8.4)

id LAA05170 for frank;Thu 25 Mar 1999 11:00:25 GMT

Date: Thu,25 Mar 1999 11:00:25 GMT

From:RHS Linux User <[historical email redacted]>

Message-Id:<[historical message identifier redacted]>

To:[historical email redacted]

Subject:test

Status:R

This is a mail test

&

mail命令还有很多其它用法,例如发送事先准备好的信件,或一次送信给若干人。还可以用其它方法送信件。

另附message的使用技巧:

当Linux系统处于多用户的情况下,有时在终端上会突然显示出下述信息:

Message from renee tty2…

并伴随出现一阵嘟嘟响声。这是用户renee想和你通话而产生的信号。若你用如下命令响应他:

$ write renee

这就建立起了你和renee的通信线路,renee在他的终端上键入的内容同时显示在你的终端上,反之你键入的内容也显示在renee的终端上。为区分终 端上哪些是你输入的,哪些是renee输入的,我们使用如下通话协议:(o)表示一段话说完,并让对方发话,(oo)代表通话结束并退出程序。

renee’s terminal: frank terminal:

[renee@xteam renee]$ write frank

$ Message from renee tty2…

$write renee

[renee@xteam renee]$Message from you tty1…

did you forget lunch? (o)

did you forgeet lunch? (o)

ten minutes (o)

ten minutes (o)

ok (oo)

ok (oo)

ctl-d

EOF

Ctl-d

EOF

[renee@xteam renee]$ $

除CTL-d键外,也可以使用DELETE退出write命令。

如果你不愿意别人干扰你的工作,可以使用mesg命令拒绝接受通话。当你向一个拒绝接收通话的用户发写命令、或者向没有注册的用户要求通话时,write命令会显示不能通话的原因。

转自:http://edu.codepub.com/2010/0413/21978.php

Leave a Reply