I was recently working on a Python script which scrapes a few things from the GitHub API to generate reports. The final result is emailed to a Google Group – old school, I know, perhaps this should just be a Slack webhook. :-)

I've been using Alpine Linux in my containers for several years now. As you may know, it's a lightweight and performant runtime environment perfectly suited to things like CI/CD pipelines which constantly spin up ephemeral tasks.

Deciding to email a report was not a particularly inspired idea... More like creative borrowing (read: plagiarism). Some other scripts I used for reference had simple cat output | mailx type stuff. Of course those were infrequently-ran, non-containerized scripts where the choice made more sense. Still, initially my uninspired mind went to a dark place – bloat up my sleek Alpine container with a bunch of mail utilities.

Let's see here, I can add in the mailx CLI (well, on Alpine it's the mailx package which provides a mail CLI – you can quickly find what you need by using the Alpine Linux package search), but that's just a MUA... Hmm, so I need to install a MTA. I suppose I can use something like Postfix, since that's a fast and nicely modularized (more secure) mail server.  Oh, then I need to configure everything.

As it turns out, your shiny new Alpine container already has everything you need to send email... The answer is BusyBox. No, it's not the latest platform for political arguments (get it, a soapbox with lots of busybody members? Oh wait, we have Facebook and Reddit for that!).

If you've been under a rock for the past decade or two, BusyBox is a venerable Swiss Army Knife of the UNIX world. I didn't make that up, they say so themselves. BusyBox bundles stripped down versions of common UNIX utilities, including sendmail, into a single CLI. Let's see what that provides:

/ # cat /etc/os-release
NAME="Alpine Linux"
PRETTY_NAME="Alpine Linux v3.11"

/ # busybox sendmail --help
BusyBox v1.31.1 () multi-call binary.

Usage: sendmail [-tv] [-f SENDER] [-amLOGIN 4<user_pass.txt | -auUSER -apPASS]
                [-w SECS] [-H 'PROG ARGS' | -S HOST] [RECIPIENT_EMAIL]...

Read email from stdin and send it

Standard options:
        -t              Read additional recipients from message body
        -f SENDER       For use in MAIL FROM:<sender>. Can be empty string
                        Default: -auUSER, or username of current UID
        -o OPTIONS      Various options. -oi implied, others are ignored
        -i              -oi synonym, implied and ignored

Busybox specific options:
        -v              Verbose
        -w SECS         Network timeout
        -H 'PROG ARGS'  Run connection helper. Examples:
                openssl s_client -quiet -tls1 -starttls smtp -connect smtp.gmail.com:25
                openssl s_client -quiet -tls1 -connect smtp.gmail.com:465
                        $SMTP_ANTISPAM_DELAY: seconds to wait after helper connect
        -S HOST[:PORT]  Server (default $SMTPHOST or
        -amLOGIN        Log in using AUTH LOGIN
        -amPLAIN        or AUTH PLAIN
                        (-amCRAM-MD5 not supported)
        -auUSER         Username for AUTH
        -apPASS         Password for AUTH

If no -a options are given, authentication is not done.
If -amLOGIN is given but no -au/-ap, user/password is read from fd #4.
Other options are silently ignored; -oi is implied.
Use makemime to create emails with attachments.
BusyBox to the rescue!

This gives you everything you need to establish TLS connections (for that you will need to apk add openssl), authenticate, send attachments, etc. My case is simpler, we have an internal Postfix relay which allows internal connections from our VPC CIDR ranges and does the heavy lifting of routing mail via Sendgrid. I simply shove a heredoc into busybox sendmail with a few arguments:

busybox sendmail -v -w 5 -S "$RELAY" -f "$FROM" "$TO" <<EOF
Subject: GitHub User Report
From: $FROM

$(diff -Buw ${REPORT} ${REPORT}.new | grep -E '^[-+]')
Sending mail with BusyBox

This generates an email by diffing the current and last reports (summarizing useful things like additions and removals of organization admins), and routes it through the configured mail relay. While it seems repetitive, the -f "$FROM" is required to avoid some spurious errors from defaulting to the UID/GID running the command. The From: header is also required, since not including it in this way will result in bounces from Google Groups. The two spaces between the email headers and body is standard message formatting. You can tweak these as needed, and in particular might not want -v which will include the full SMTP session in output (including connection negotiation and data).

With these, we can generate spam er I mean useful reports with the best of them... Taking it one step further, we don't have to call sendmail via busybox. Alpine has a number of symlinks for common utilities. In this case we can just call sendmail directly, and the name of the symlink is used by BusyBox to invoke the desired functionality:

/ # ls -l /usr/sbin/sendmail
lrwxrwxrwx    1 root     root            12 Jan 16 21:52 /usr/sbin/sendmail -> /bin/busybox

/ # sendmail --help
BusyBox v1.31.1 () multi-call binary.

Usage: sendmail [-tv] [-f SENDER] [-amLOGIN 4<user_pass.txt | -auUSER -apPASS]
                [-w SECS] [-H 'PROG ARGS' | -S HOST] [RECIPIENT_EMAIL]...
Symlinks FTW!