Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
Although now one of the oldest electronic communication mediums, electronic mail (emails, e-mails) are still around and being used for various purposes:
Due to many of its automated applications, having a way to conveniently send mail via the command line can be essential.
In this tutorial, we’ll discusses how to use curl to send e-mails from the shell. First, we look at the curl tool in general. Next, we explore what sending mail from the command line involves. Finally, we get to different ways of sending mail using curl.
We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments unless otherwise specified.
The curl utility is ubiquitous when it comes to data transfer. It supports a vast amount of protocols and standards:
Let’s see an example with HTTPS:
$ curl --data-urlencode "user=USERNAME" --data-urlencode "pass=PASSWORD" https://gerganov.com/
{
"user": "USERNAME",
"pass": "PASSWORD"
}
In this case, we use curl to send a basic HTTPS request to https://gerganov.com, which also includes two parameters, user and pass, via the –data-urlencode option. As a result, the server returns the passed parameter and its value in a JSON format.
In fact, were we to need standard authentication, curl can facilitate that via its own –user flag:
$ curl --user USERNAME:PASSWORD https://www.gerganov.com/
These features, along with configuration options, make curl a versatile communication tool. In fact, we can also employ it for processing mail protocol messages.
As we already saw, curl supports most major mail protocols. Because of this, we can use curl to send mails from the terminal. Let’s see how.
To begin with, sending mail involves an actual service that can accept our request to send an e-mail and handle it appropriately. This service is usually called a mail provider or mail server and comprises several components:
In this context, a mail server is often synonymous with an SMTP server.
First, we find an appropriate working SMTP server:
$ nc -v smtp.protonmail.ch 587
Connection to smtp.protonmail.ch (176.119.200.135) 587 port [tcp/submission] succeeded!
220 mailsub002.protonmail.ch ESMTP Postfix
In this case, we use netcat (nc) in [-v]erbose mode to ensure we can connect to the smtp.protonmail.ch mail server at the default port 587. We can do the same with servers like smtp.gmail.com and other default ports such as 465.
Naturally, we can host our own server locally as well.
Regardless of the mail server, we need to have a way to authenticate.
In many cases, this involves a username and password. To acquire these, we can either create an account in the shell of our own hosted mail server or, more commonly, register for an account with an online mail service provider like ProtonMail, Gmail, and similar.
After doing so, we usually get a set of credentials:
Critically, many providers have or had separate settings to allow direct basic authentication with a cleartext password. So, if we don’t use a self-hosted mail server, we may have to go a step further by enabling insecure access. This means enabling the server to authenticate via cleartext passwords.
However, some providers, such as Google, don’t support this old authentication method, so they have another feature.
Since passing passwords around isn’t secure or in sync with best practices, passkeys or tokens, also called app passwords, come into play. These comprise data in addition to or instead of a regular password or login session that ensure only authorized users are given access.
For example, multi-factor authentication (MFA) or two-factor authentication (2FA) schemes employ such additional measures. Usually, when we configure and enable either, we get a permanent or one-time passkey token that we can use instead of a password when authentication is required. How we perform the necessary configuration varies according to the service, deployment, or provider.
In most cases, this means we have to perform at least two steps:
After doing the above, we can use our new credentials or token when authenticating to the server of our choice for sending or receiving mail. Let’s explore how we can do this.
After acquiring the necessary information, we can just issue curl commands to send emails.
In all cases, we use variables with self-explanatory names to store the relevant information. Critically, $PASS can be either a password or a preconfigured passkey or token, so $USER can be the same as or different than $SENDER_ADDRESS, depending on the service.
To get progress and error details from each command, enabling us to troubleshoot issues more easily, we can also employ the –verbose (-v) flag.
Let’s start off by sending a basic e-mail to a single recipient:
$ curl --ssl-reqd --url "smtps://$SERVER:$PORT" \
--user "$USER:$PASS" \
--mail-from "$SENDER_ADDRESS" \
--mail-rcpt "$RECIPIENT_ADDRESS" \
--upload-file "$MAIL_CONTENT_FILE"
For clarity, we employ the backslash to format the command on several lines.
Technically, we use several curl options:
In this case, we use $SERVER for the mail server domain name or IP address and $PORT for the respective SMTP(S) port.
Let’s explore some example values for each variable:
SERVER='smtp.example.com'
PORT='465'
USER='[email protected]'
PASS='PASSWORD'
SENDER_ADDRESS='[email protected]'
RECIPIENT_ADDRESS='[email protected]'
MAIL_CONTENT_FILE='mailfile'
Finally, $MAIL_CONTENT_FILE is a path to a file that contains the message text. Alternatively, we can pass this data via stdin by using a – dash instead of a file name or directly leveraging here-strings.
The $MAIL_CONTENT_FILE file or standard input can just contain a simple text message representing the mail body, as we can see with cat:
$ cat mailfile
This is the body of the e-mail.
However, we can also prepend colon-separated headers on different lines according to SMTP:
HEADER_NAME: HEADER_VALUE
There are a number of standard headers, also known as header fields, some of which are more common than others:
If we omit a recipient in the headers, but add it as –mail-rcpt, we end up with a blind carbon copy, i.e., a recipient hidden from other recipients. The general structure above is defined in RFC 5322 – Internet Message Format. Each header uses the ASCII encoding. However, the actual body can employ different MIME formats, not only text/plain.
Let’s see a more complex and comprehensive example of an email:
$ cat mailfile
From: "Hiks Gerganov" <[email protected]>
To: "Michał Aibin" <[email protected]>
Subject: Basic Message
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
<b>Bolded text</b>
Now, let’s use this file with our earlier syntax:
$ MAIL_CONTENT_FILE=mailfile
$ curl --ssl-reqd --url "smtps://$SERVER:$PORT" \
--user "$USER:$PASS" \
--mail-from "$SENDER_ADDRESS" \
--mail-rcpt "$RECIPIENT_ADDRESS" \
--upload-file "$MAIL_CONTENT_FILE"
Importantly, if $SENDER_ADDRESS differs from the addresses in From:, servers might reject our mail due to obfuscation.
For all headers we specify in the mail file, we can also use the –header (-H) option of curl directly.
For example, let’s create a Bash script called xsendmail.sh to demonstrate this:
$ cat xsendmail.sh
#!/usr/bin/env bash
SERVER='smtp.example.com'
PORT='465'
USER='[email protected]'
PASS='PASSWORD'
SENDER_ADDRESS="$USER"
SENDER_NAME='SENDER'
RECIPIENT_NAME='y'
RECIPIENT_ADDRESS='[email protected]'
SUBJECT='Scripted Mail'
MESSAGE=$'Line 1\nLine 2'
curl --ssl-reqd --url "smtps://$SERVER:$PORT" \
--user "$USER:$PASS" \
--mail-from "$SENDER_ADDRESS" \
--mail-rcpt "$RECIPIENT_ADDRESS" \
--header "Subject: $SUBJECT" \
--header "From: $SENDER_NAME <$SENDER_ADDRESS>" \
--header "To: $RECIPIENT_NAME <$RECIPIENT_ADDRESS>" \
--form '=(;type=multipart/mixed' --form "=$MESSAGE;type=text/plain" --form '=)'
Here, we used the –form (-F) option to specify a given format for the mail body in $MESSAGE. In this case, $MESSAGE uses ANSI-C quoting to insert newlines and other escape characters.
At this point, we can make the script executable via chmod and run it:
$ chmod +x xsendmail.sh
$ ./xsendmail.sh
Of course, we can use command-line arguments or basic read prompts for any and all parameters, including attachments.
In addition to data and metadata, we can also add attachments to emails:
$ cat xsendmail.sh
#!/usr/bin/env bash
SERVER='smtp.example.com'
PORT='465'
USER='[email protected]'
PASS='PASSWORD'
SENDER_ADDRESS="$1"
SENDER_NAME='SENDER'
RECIPIENT_NAME='y'
RECIPIENT_ADDRESS='[email protected]'
SUBJECT='Scripted Mail'
MESSAGE=$'Line 1\nLine 2'
ATTACHMENT_FILE='/home/user/att1'
ATTACHMENT_TYPE="$(file --mime-type '$ATTACHMENT_FILE' | sed 's/.*: //')"
curl --ssl-reqd --url "smtps://$SERVER:$PORT" \
--user "$USER:$PASS" \
--mail-from "$SENDER_ADDRESS" \
--mail-rcpt "$RECIPIENT_ADDRESS" \
--header "Subject: $SUBJECT" \
--header "From: $SENDER_NAME <$SENDER_ADDRESS>" \
--header "To: $RECIPIENT_NAME <$RECIPIENT_ADDRESS>" \
--form '=(;type=multipart/mixed' --form "=$MESSAGE;type=text/plain"
--form "file=@$ATTACHMENT_FILE;type=$ATTACHMENT_TYPE;encoder=base64" --form '=)'
By modifying our earlier script, we made several additions:
Importantly, it’s vital to ensure the attachment file exists, as the ATTACHMENT_TYPE assignment operation might otherwise fail and cause a syntax problem. As a result, the mail might not get sent and we can end up with an unexpected error.
In this article, we explored the requirements and methods for sending e-mails via curl.
In conclusion, although the core concept is fairly basic, sending mail from the command line may involve a number of configuration options on both the server and client side.