Awk snippet: send email to multiple recipients with cURL
Posted on
As I experiment with Neomutt, I wanted to keep being able to enqueue emails for sending later like my previous setup, so that I didn’t rely on having an internet connection.
My requirements for the sendmail command were:
-
store the email in a file, and send it later;
-
send from different addresses, using different SMTP servers.
I couldn’t find an MTA that could accomplish that, but I was able to quickly write a solution.
The first part was the easiest: store the email in a file:
Now that I had the email file store locally, I needed a program to send the email from the file, so that I could create a cronjob like:
The dispatch-email.sh would have to look at the From: header and decide which
SMTP server to use. As I found
out that curl supports SMTP and is able to send emails, this is what I
ended up with:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#!/bin/sh -eu
F="$1"
rcpt="$(awk '
match($0, /^(To|Cc|Bcc): (.*)$/, m) {
split(m[2], tos, ",")
for (i in tos) {
print "--mail-rcpt " tos[i]
}
}
' "$F")"
if grep -qE '^From: .*<addr@server1\.org>$' "$F"; then
curl \
-s \
--url smtp://smtp.server1.org:587 \
--ssl-reqd \
--mail-from addr@server1.org \
$rcpt \
--user 'addr@server1.org:my-long-and-secure-passphrase' \
--upload-file "$F"
elif grep -qE '^From: .*<addr@server2\.org>$' "$F"; then
curl \
-s \
--url smtp://smtp.server2.org:587 \
--ssl-reqd \
--mail-from addr@server2.org \
$rcpt \
--user 'addr@server2.org:my-long-and-secure-passphrase' \
--upload-file "$F"
else
echo 'Bad "From: " address'
exit 1
fi
|
Most of curl flags used are self-explanatory, except for $rcpt.
curl connects to the SMTP server, but doesn’t set the recipient address by looking at the message. My
solution was to generate the curl flags, store them in $rcpt and use it unquoted to leverage
shell word splitting.
To me, the most interesting part was building the $rcpt flags. My first instinct was to try
grep, but it couldn’t print only matches in a regex. As I started to turn towards sed, I envisioned needing
something else to loop over the sed output, and I then moved to Awk.
In the short Awk snippet, 3 things were new to me: the match(…), split(…) and
for () {}. The only other function I have ever used was gsub(…), but these new
ones felt similar enough that I could almost guess their behaviour and arguments. match(…)
stores the matches of a regex on the given array positionally, and split(…) stores the chunks
in the given array.
I even did it incrementally:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ H='To: to@example.com, to2@example.com\nCc: cc@example.com, cc2@example.com\nBcc: bcc@example.com,bcc2@example.com\n'
$ printf "$H" | awk '/^To: .*$/ { print $0 }'
To: to@example.com, to2@example.com
$ printf "$H" | awk 'match($0, /^To: (.*)$/, m) { print m }'
awk: ligne de commande:1: (FILENAME=- FNR=1) fatal : tentative d'utilisation du tableau « m » dans un contexte scalaire
$ printf "$H" | awk 'match($0, /^To: (.*)$/, m) { print m[0] }'
To: to@example.com, to2@example.com
$ printf "$H" | awk 'match($0, /^To: (.*)$/, m) { print m[1] }'
to@example.com, to2@example.com
$ printf "$H" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, " "); print tos }'
awk: ligne de commande:1: (FILENAME=- FNR=1) fatal : tentative d'utilisation du tableau « tos » dans un contexte scalaire
$ printf "$H" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, " "); print tos[0] }'
$ printf "$H" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, " "); print tos[1] }'
to@example.com,
$ printf "$H" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, " "); print tos[2] }'
to2@example.com
$ printf "$H" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, " "); print tos[3] }'
|
(This isn’t the verbatim interactive session, but a cleaned version to make it more readable.)
At this point, I realized I needed a for loop over the tos array, and I moved the Awk snippet
into the ~/bin/dispatch-email.sh. I liked the final thing:
As I learn more about Awk, I feel that it is too undervalued, as many people turn to Perl or other programming languages when Awk suffices. The advantage is pretty clear: writing programs that run on any POSIX system, without extra dependencies required.
Coding to the standards is underrated.