Email API

Send Email from a Bash Script with curl (Cron Reports and Backup Done)

A complete bash script that sends email with curl through an HTTPS API: JSON escaping with jq, capturing output and exit codes, cron, and key storage.

You have a backup script, a nightly report, or a cleanup job, and you want an email when it finishes. Setting up sendmail or postfix on a server just for that is a chore and the mail usually lands in spam anyway. A single curl call to an email API does it in five lines, from your own domain, with proper authentication. Here is a script you can copy, and the details that keep it working in cron.

Quick answer

Put your API key in a file readable only by root (or the cron user), build a JSON body with jq so quotes and newlines are escaped correctly, and POST it with curl. Wrap the real job in a function, capture its output and exit status, and choose the subject based on that status. Cron sees only the exit code of the script, so make the email the thing that reports.

The script

Save as /usr/local/bin/backup-and-report.sh and make it executable. It requires curl and jq, both available in every major distribution's package manager.

#!/usr/bin/env bash
set -uo pipefail

OQUMAIL_API_KEY=$(cat /etc/oqumail.key)
FROM="backups@yourdomain.com"
TO="ops@yourdomain.com"
HOST=$(hostname -f)

# --- the actual job ---
OUTPUT=$( (pg_dump -Fc mydb > /backups/mydb-$(date +%F).dump) 2>&1 )
STATUS=$?
SIZE=$(du -h /backups/mydb-$(date +%F).dump 2>/dev/null | cut -f1)

if [ "$STATUS" -eq 0 ]; then
  SUBJECT="[OK] Backup of mydb on $HOST ($SIZE)"
else
  SUBJECT="[FAILED] Backup of mydb on $HOST (exit $STATUS)"
fi

BODY=$(jq -n --arg from "$FROM" --arg to "$TO" --arg subject "$SUBJECT" \
  --arg text "Host: $HOST
Exit: $STATUS
Size: $SIZE

$OUTPUT" \
  --arg html "<p>Host: $HOST<br>Exit: $STATUS<br>Size: $SIZE</p><pre>$(printf '%s' "$OUTPUT" | sed 's/&/\&amp;/g; s/</\&lt;/g')</pre>" \
  '{from:$from, to:$to, subject:$subject, html:$html, text:$text}')

curl -sS -o /dev/null -w "%{http_code}\n" -X POST https://api.oqumail.com/api/v1/emails \
  -H "Authorization: Bearer $OQUMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$BODY"

exit $STATUS

Why jq and not string concatenation

The single most common bug in shell email scripts is a body that breaks JSON: a double quote in the output, a backslash in a Windows path, or a newline. jq --arg takes raw strings and escapes them correctly, so the output of any command can go straight into the text field. The html field additionally needs angle brackets and ampersands escaped, which the sed in the script handles; without it, a log line containing <script> would become markup.

Scheduling it in cron

  1. Store the key: echo "oqm_live_..." > /etc/oqumail.key and chmod 600 /etc/oqumail.key, owned by the user that runs the job.
  2. Test by hand first: run the script and check for a 202 line from curl and the email in your inbox.
  3. Add to crontab with an absolute path and a full PATH, because cron's environment is minimal: 0 2 * * * PATH=/usr/local/bin:/usr/bin:/bin /usr/local/bin/backup-and-report.sh >> /var/log/backup-report.log 2>&1
  4. Cron runs in the server's local time zone unless CRON_TZ is set; put the time zone in the subject or use UTC everywhere.
  5. If the server has no outbound port 25 (most clouds block it) none of this matters; HTTPS on 443 is all you need.

Variations

  • Report only on failure: wrap the curl in if [ "$STATUS" -ne 0 ]. Add a weekly "still alive" email so silence is not mistaken for success.
  • Attach a report: do not. Put the numbers in the body and link to a file on your server or object storage.
  • Multiple recipients: call the API once per address in a loop; keep the loop small.
  • Send from a Docker container: mount the key file read-only and install curl and jq in the image (apk add curl jq on Alpine).
  • Timeouts: add --max-time 20 to curl so a network hiccup does not hang the script.

What you get from an API instead of sendmail

Mail from a random VPS through local sendmail has no DKIM signature, usually no SPF alignment, and often no reverse DNS, so Gmail files it under spam or drops it. Sending via OquMail's API means the message goes out from your verified domain with DKIM, authenticated by a Bearer key, and each one appears in the delivery log with the receiving server's SMTP response. The ops@ address it goes to can be a real OquMail mailbox on the same domain, read in webmail or any IMAP client, with no extra cost on the free plan.

Common questions

Can I do this without jq?

You can escape by hand with printf and sed, but you will get it wrong on the first log line containing a quote. jq is a 1 MB package; install it.

How do I know the email was sent if cron output goes nowhere?

The script writes the HTTP status code to its log file; 202 means accepted. For anything else, the response body from the API says why (401 for a bad key, 400 for a malformed body).

Is it safe to put the key in the crontab line?

No, crontab contents are visible to other admins and show up in process listings if passed as arguments. Keep it in a 600-permission file and read it inside the script.

Free business email on your own domain

OquMail gives you up to 15 mailboxes on your domain — free — with guided SPF/DKIM/DMARC, webmail, IMAP/SMTP for any mail app, and a send API. Most teams are live in under fifteen minutes. Start at oqumail.com.

Get started free

Ready for business email on your domain?

Up to 15 free mailboxes, guided DNS, webmail, and a transactional API — start in minutes.

Create your free workspace