# msgid "" msgstr "" msgid "title: 'Awk snippet: send email to multiple recipients with cURL'" msgstr "" msgid "date: 2021-01-12" msgstr "" msgid "layout: post" msgstr "" msgid "lang: en" msgstr "" msgid "ref: awk-snippet-send-email-to-multiple-recipients-with-curl" msgstr "" msgid "My requirements for the `sendmail` command were:" msgstr "" msgid "store the email in a file, and send it later." msgstr "" msgid "send from different addresses, using different SMTP servers;" msgstr "" msgid "" "I couldn't find an MTA that could accomplish that, but I was able to quickly" " write a solution." msgstr "" msgid "The first part was the easiest: store the email in a file:" msgstr "" msgid "" "# ~/.config/mutt/muttrc:\n" "set sendmail=~/bin/enqueue-email.sh\n" "\n" "# ~/bin/enqueue-email.sh:\n" "#!/bin/sh -eu\n" "\n" "cat - > \"$HOME/mbsync/my-queued-emails/$(date -Is)\"\n" msgstr "" msgid "" "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:" msgstr "" msgid "" "for f in ~/mbsync/my-queued-emails/*; do\n" " ~/bin/dispatch-email.sh \"$f\" && rm \"$f\"\n" "done\n" msgstr "" msgid "" "The `dispatch-email.sh` would have to look at the `From: ` header and decide" " which SMTP server to use. As I [found " "out](https://blog.edmdesigner.com/send-email-from-linux-command-line/) that " "[curl](https://curl.se/) supports SMTP and is able to send emails, this is " "what I ended up with:" msgstr "" msgid "Most of curl flags used are self-explanatory, except for `$rcpt`." msgstr "" msgid "" "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." msgstr "" msgid "" "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." msgstr "" msgid "" "$ H='To: to@example.com, to2@example.com\\nCc: cc@example.com, cc2@example.com\\nBcc: bcc@example.com,bcc2@example.com\\n'\n" "$ printf \"$H\" | awk '/^To: .*$/ { print $0 }'\n" "To: to@example.com, to2@example.com\n" "$ printf \"$H\" | awk 'match($0, /^To: (.*)$/, m) { print m }'\n" "awk: ligne de commande:1: (FILENAME=- FNR=1) fatal : tentative d'utilisation du tableau « m » dans un contexte scalaire\n" "$ printf \"$H\" | awk 'match($0, /^To: (.*)$/, m) { print m[0] }'\n" "To: to@example.com, to2@example.com\n" "$ printf \"$H\" | awk 'match($0, /^To: (.*)$/, m) { print m[1] }'\n" "to@example.com, to2@example.com\n" "$ printf \"$H\" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, \" \"); print tos }'\n" "awk: ligne de commande:1: (FILENAME=- FNR=1) fatal : tentative d'utilisation du tableau « tos » dans un contexte scalaire\n" "$ printf \"$H\" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, \" \"); print tos[0] }'\n" "\n" "$ printf \"$H\" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, \" \"); print tos[1] }'\n" "to@example.com,\n" "$ printf \"$H\" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, \" \"); print tos[2] }'\n" "to2@example.com\n" "$ printf \"$H\" | awk 'match($0, /^To: (.*)$/, m) { split(m[1], tos, \" \"); print tos[3] }'\n" "\n" msgstr "" msgid "" "(This isn't the verbatim interactive session, but a cleaned version to make " "it more readable.)" msgstr "" msgid "" "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:" msgstr "" msgid "" "match($0, /^(To|Cc|Bcc): (.*)$/, m) {\n" " split(m[2], tos, \",\")\n" " for (i in tos) {\n" " print \"--mail-rcpt \" tos[i]\n" " }\n" "}\n" msgstr "" msgid "" "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." msgstr "" msgid "Coding to the standards is underrated." msgstr "" msgid "" "#!/bin/sh -eu\n" "\n" "F=\"$1\"\n" "\n" "rcpt=\"$(awk '\n" " match($0, /^(To|Cc|Bcc): (.*)$/, m) {\n" " split(m[2], tos, \",\")\n" " for (i in tos) {\n" " print \"--mail-rcpt \" tos[i]\n" " }\n" " }\n" "' \"$F\")\"\n" "\n" "if grep -qE '^From: .*$' \"$F\"; then\n" " curl \\\n" " -s \\\n" " --url smtp://smtp.server1.org:587 \\\n" " --ssl-reqd \\\n" " --mail-from addr@server1.org \\\n" " $rcpt \\\n" " --user 'addr@server1.org:my-long-and-secure-passphrase' \\\n" " --upload-file \"$F\"\n" "elif grep -qE '^From: .*$' \"$F\"; then\n" " curl \\\n" " -s \\\n" " --url smtp://smtp.server2.org:587 \\\n" " --ssl-reqd \\\n" " --mail-from addr@server2.org \\\n" " $rcpt \\\n" " --user 'addr@server2.org:my-long-and-secure-passphrase' \\\n" " --upload-file \"$F\"\n" "else\n" " echo 'Bad \"From: \" address'\n" " exit 1\n" "fi\n" msgstr "" msgid "" "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." msgstr "" msgid "I even did it incrementally:" msgstr "" msgid "" "As I experiment with [Neomutt](https://neomutt.org/), 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." msgstr "" #~ msgid "" #~ "As I experimented with [Neomutt](https://neomutt.org/), 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." #~ msgstr "" #~ msgid "I even did this incrementally:" #~ msgstr "" #~ msgid "" #~ "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 felt similar enough that I could almost guess " #~ "their behaviour. `match(...)` stores the matches of a regex on the given " #~ "array positionally, and `split(...)` stores the chunks in the given array." #~ msgstr "" #~ msgid "" #~ "As I experimented with [Neomutt](https://neomutt.org/), I wanted to keep " #~ "being able to enqueue emails for sending later, so that I didn't rely on " #~ "having an internet connection." #~ msgstr "" #~ msgid "" #~ "#!/bin/sh -eu\n" #~ "\n" #~ "F=\"$1\"\n" #~ "\n" #~ "rcpt=\"$(awk '\n" #~ " match($0, /^(To|Cc|Bcc): (.*)$/, m) {\n" #~ " split(m[2], tos, \",\")\n" #~ " for (i in tos) {\n" #~ " print \"--mail-rcpt \" tos[i]\n" #~ " }\n" #~ " }\n" #~ "' \"$F\")\"\n" #~ "\n" #~ "if grep -qE '^From: .*$' \"$F\"; then\n" #~ " curl \\\n" #~ " -s \\\n" #~ " --url smtp://smtp.server1.org:587 \\\n" #~ " --ssl-reqd \\\n" #~ " --mail-from addr@server1.org \\\n" #~ " $rcpt \\\n" #~ " --user 'addr@server1.org:my-long-and-secure-passphrase' \\\n" #~ " --upload-file \"$F\"\n" #~ "eliif grep -qE '^From: .*$' \"$F\"; then\n" #~ " curl \\\n" #~ " -s \\\n" #~ " --url smtp://smtp.server2.org:587 \\\n" #~ " --ssl-reqd \\\n" #~ " --mail-from addr@server2.org \\\n" #~ " $rcpt \\\n" #~ " --user 'addr@server2.org:my-long-and-secure-passphrase' \\\n" #~ " --upload-file \"$F\"\n" #~ "else\n" #~ " echo 'Bad \"From: \" address'\n" #~ " exit 1\n" #~ "fi\n" #~ msgstr ""