From 020c1e77489b772f854bb3288b9c8d2818a6bf9d Mon Sep 17 00:00:00 2001
From: EuAndreh <eu@euandre.org>
Date: Fri, 18 Apr 2025 02:17:12 -0300
Subject: git mv src/content/* src/content/en/

---
 .../en/tils/2021/01/12/curl-awk-emails.adoc        | 148 +++++++++++++++++++++
 1 file changed, 148 insertions(+)
 create mode 100644 src/content/en/tils/2021/01/12/curl-awk-emails.adoc

(limited to 'src/content/en/tils/2021/01/12/curl-awk-emails.adoc')

diff --git a/src/content/en/tils/2021/01/12/curl-awk-emails.adoc b/src/content/en/tils/2021/01/12/curl-awk-emails.adoc
new file mode 100644
index 0000000..d432da2
--- /dev/null
+++ b/src/content/en/tils/2021/01/12/curl-awk-emails.adoc
@@ -0,0 +1,148 @@
+= Awk snippet: send email to multiple recipients with cURL
+
+:neomutt: https://neomutt.org/
+:found-out-article: https://blog.edmdesigner.com/send-email-from-linux-command-line/
+:curl: https://curl.se/
+
+As I experiment with {neomutt}[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:
+
+[source,sh]
+----
+# ~/.config/mutt/muttrc:
+set sendmail=~/bin/enqueue-email.sh
+
+# ~/bin/enqueue-email.sh:
+#!/bin/sh -eu
+
+cat - > "$HOME/mbsync/my-queued-emails/$(date -Is)"
+----
+
+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:
+
+[source,sh]
+----
+for f in ~/mbsync/my-queued-emails/*; do
+  ~/bin/dispatch-email.sh "$f" && rm "$f"
+done
+----
+
+The `dispatch-email.sh` would have to look at the `From:` header and decide
+which SMTP server to use.  As I {found-out-article}[found out] that {curl}[curl]
+supports SMTP and is able to send emails, this is what I ended up with:
+
+[source,sh]
+----
+#!/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:
+
+[source,sh]
+----
+$ 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:
+
+[source,awk]
+----
+match($0, /^(To|Cc|Bcc): (.*)$/, m) {
+  split(m[2], tos, ",")
+  for (i in tos) {
+    print "--mail-rcpt " tos[i]
+  }
+}
+----
+
+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.
-- 
cgit v1.2.3

