Guide

Send Email with curl from the Terminal

curl has spoken SMTP since 2010, so you can send an email with a single command — no mail server, no Python script. The catch is everything around it: an app password, an unblocked port, and a message you assemble by hand. Here's the working syntax, the three things that break it, and when one command does the whole job instead.

Written by Pouya Sanooei Software Engineer

VerifiedCLI 3.1.16 · Gmail, Outlook · last tested June 8, 2026

Command references used in this guide: nylas email send and nylas init.

How do you send email with curl?

curl sends email by acting as an SMTP client: you point it at a mail server with a smtps:// URL, authenticate with --user, name the envelope sender and recipient, and upload an RFC 5322 message as the body. curl has shipped SMTP support since version 7.20 in 2010, so the binary on almost any machine can do it without extra tools.

The message itself is a plain text file with From, To, and Subject headers, a blank line, then the body. curl uploads it verbatim — it does not add headers for you, as the curl SMTP documentation notes. The example below sends through Gmail's submission server on port 465, which wraps the whole session in TLS from the first byte.

# Build the message (headers, blank line, body)
cat > mail.txt <<'EOF'
From: You <you@gmail.com>
To: Recipient <recipient@example.com>
Subject: Sent with curl

This message left the terminal through Gmail's SMTP server.
EOF

# Send it (port 465 = implicit TLS)
curl --ssl-reqd \
  --url 'smtps://smtp.gmail.com:465' \
  --user 'you@gmail.com:your-app-password' \
  --mail-from 'you@gmail.com' \
  --mail-rcpt 'recipient@example.com' \
  --upload-file mail.txt

Why does curl email need an app password?

Gmail rejects your normal account password over SMTP. Since Google removed Less Secure Apps access in September 2024, plain-password SMTP no longer works for personal Gmail; you must generate a 16-character app password under an account with 2-Step Verification enabled. That app password goes in curl's --user field. Outlook personal accounts deprecated Basic Auth for SMTP in September 2024 as well.

Port choice is the other variable. Port 465 uses implicit TLS (smtps://), and port 587 uses STARTTLS (smtp:// with --ssl-reqd) — both are encrypted, and Gmail accepts either. Port 25 is for server-to-server relay and is blocked outbound by nearly every residential ISP and cloud provider, so it almost never works from a laptop or a CI runner.

Portcurl URLEncryptionWorks from a laptop?
465smtps://Implicit TLSYes
587smtp:// + --ssl-reqdSTARTTLSYes
25smtp://Often noneBlocked by most ISPs

How does curl compare to msmtp and the Nylas CLI?

Three tools send mail from a terminal, and they trade convenience for control differently. curl is preinstalled but makes you assemble the message and manage the app password. msmtp is a sendmail-compatible SMTP client with a config file, lighter to script once set up. The Nylas CLI authenticates with OAuth once, then sends across six providers with no SMTP settings at all.

DimensioncurlmsmtpNylas CLI
InstallPreinstalledapt/brewOne command
AuthApp passwordApp passwordOAuth 2.0
Build the messageBy handBy handFlags
AttachmentsManual MIMEManual MIMEdrafts create --attach
ProvidersAny SMTP hostAny SMTP host6 (no SMTP config)
Read / search inboxNo (send only)No (send only)Yes

When does the curl SMTP one-liner break?

The one-liner holds up for a plain-text test message and falls apart the moment you need more. Attachments require you to hand-build a multipart/mixed MIME envelope with boundaries and base64 encoding — dozens of lines that a typo silently corrupts. HTML bodies need a second MIME part. And nothing refreshes the app password, so a rotated credential breaks the script with a terse 535 5.7.8 authentication error.

Port blocking is the failure that wastes the most time. A script that works on your laptop returns Connection timed out on a cloud VM because the provider blocks outbound 465 and 587 by default to fight spam — AWS throttles port 25 on EC2 until you request removal. The fix isn't in your curl flags; it's a firewall rule you may not control. That's the point where a different transport saves the afternoon.

# What an attachment actually costs in curl: hand-built MIME
cat > mail.txt <<'EOF'
From: you@gmail.com
To: recipient@example.com
Subject: Report attached
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="X_BOUNDARY"

--X_BOUNDARY
Content-Type: text/plain

See the attached report.
--X_BOUNDARY
Content-Type: application/pdf; name="report.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"

$(base64 report.pdf)
--X_BOUNDARY--
EOF
# ...then the same curl invocation as before. One wrong boundary = broken mail.

How do you send without SMTP config using the CLI?

The nylas email send command sends across Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP without any SMTP host, port, or app password. You authenticate once with nylas init over OAuth 2.0; the token lives in your system keyring and refreshes automatically every 3,600 seconds. Attachments, HTML, and scheduling are flags, not hand-built MIME.

Because auth is OAuth rather than a stored password, there's no credential to rotate in a cron file and no app password to leak. The same command runs identically on a laptop and a CI runner, since it talks to the Nylas API over HTTPS port 443 — never the blocked SMTP ports. Output is JSON with --json for piping into scripts.

# Authenticate once (OAuth — no app password, no SMTP host)
nylas init

# Send plain text
nylas email send --to recipient@example.com \
  --subject "Sent with the CLI" \
  --body "No SMTP host, no app password, no MIME boundaries."

# Attach a file — no manual base64 (compose a draft, then send it)
nylas email drafts create --to recipient@example.com \
  --subject "Report attached" \
  --body "See attached." \
  --attach report.pdf --quiet
nylas email drafts send <draft-id>

curl is the right tool when you must hit an arbitrary SMTP relay and the message is trivial. For anything you'll run twice — a cron job, a CI notification, a script that attaches a file — the OAuth path removes the three things that break curl. See send email from the terminal for the full command and cron email without Postfix for the scheduled version.

Next steps