Tools: Build a daily email digest with cron and 6 lines of bash - Guide

Tools: Build a daily email digest with cron and 6 lines of bash - Guide

What you need

The script

The cron entry

Why this beats the alternatives

Make it nicer

HTML body for charts

Sparklines from the last 7 days

Attachment with the raw CSV

Skip weekends or holidays

A common gotcha

Going further

Next steps Every product team rebuilds the same thing: a 7 AM email summarising what happened yesterday. Signups. Errors. New customers. Pipeline counts. Some teams use Looker. Some pay Zapier. Some have a sheet that nobody opens. The honest answer for most cases: 6 lines of bash, one cron entry, ship. Save as /opt/scripts/digest.sh: That is it. Six lines if you count the shebang and the set directive. 7 AM, Monday through Friday, run as the ops user. Logs to syslog by default — journalctl -u cron to inspect. For 90% of "email me yesterday's numbers" requests, the script wins. The other 10% need charts, drill-down, or non-engineering authoring — that is when Looker pays back. Cron runs with a minimal PATH. If nylas is in ~/.config/nylas/bin (the default install location), either symlink it to /usr/local/bin/nylas or set PATH at the top of your script: If your job runs as a different user (e.g., ops), the install needs to be visible to that user. Six lines is the floor, not the ceiling. Most teams stay near the floor for years. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ -weight: 500;">brew -weight: 500;">install nylas/nylas-cli/nylas # or: -weight: 500;">curl -fsSL https://cli.nylas.com/-weight: 500;">install.sh | bash nylas auth config --api-key YOUR_KEY -weight: 500;">brew -weight: 500;">install nylas/nylas-cli/nylas # or: -weight: 500;">curl -fsSL https://cli.nylas.com/-weight: 500;">install.sh | bash nylas auth config --api-key YOUR_KEY -weight: 500;">brew -weight: 500;">install nylas/nylas-cli/nylas # or: -weight: 500;">curl -fsSL https://cli.nylas.com/-weight: 500;">install.sh | bash nylas auth config --api-key YOUR_KEY #!/usr/bin/env bash set -euo pipefail SIGNUPS=$(psql -tA -h db.example.com -U readonly app -c "SELECT count(*) FROM users WHERE created_at > now() - interval '1 day'") ERRORS=$(psql -tA -h db.example.com -U readonly app -c "SELECT count(*) FROM events WHERE level='error' AND created_at > now() - interval '1 day'") REVENUE=$(psql -tA -h db.example.com -U readonly app -c "SELECT coalesce(sum(amount_cents), 0)/100 FROM payments WHERE created_at > now() - interval '1 day'") nylas email send --to [email protected] \ --subject "Daily digest $(date +%F)" \ --body "Signups: $SIGNUPS\nErrors: $ERRORS\nRevenue: \$$REVENUE" #!/usr/bin/env bash set -euo pipefail SIGNUPS=$(psql -tA -h db.example.com -U readonly app -c "SELECT count(*) FROM users WHERE created_at > now() - interval '1 day'") ERRORS=$(psql -tA -h db.example.com -U readonly app -c "SELECT count(*) FROM events WHERE level='error' AND created_at > now() - interval '1 day'") REVENUE=$(psql -tA -h db.example.com -U readonly app -c "SELECT coalesce(sum(amount_cents), 0)/100 FROM payments WHERE created_at > now() - interval '1 day'") nylas email send --to [email protected] \ --subject "Daily digest $(date +%F)" \ --body "Signups: $SIGNUPS\nErrors: $ERRORS\nRevenue: \$$REVENUE" #!/usr/bin/env bash set -euo pipefail SIGNUPS=$(psql -tA -h db.example.com -U readonly app -c "SELECT count(*) FROM users WHERE created_at > now() - interval '1 day'") ERRORS=$(psql -tA -h db.example.com -U readonly app -c "SELECT count(*) FROM events WHERE level='error' AND created_at > now() - interval '1 day'") REVENUE=$(psql -tA -h db.example.com -U readonly app -c "SELECT coalesce(sum(amount_cents), 0)/100 FROM payments WHERE created_at > now() - interval '1 day'") nylas email send --to [email protected] \ --subject "Daily digest $(date +%F)" \ --body "Signups: $SIGNUPS\nErrors: $ERRORS\nRevenue: \$$REVENUE" # /etc/cron.d/digest 0 7 * * 1-5 ops bash /opt/scripts/digest.sh # /etc/cron.d/digest 0 7 * * 1-5 ops bash /opt/scripts/digest.sh # /etc/cron.d/digest 0 7 * * 1-5 ops bash /opt/scripts/digest.sh nylas email send --to [email protected] \ --subject "Daily digest $(date +%F)" \ --html "<h1>Daily digest</h1><table><tr><th>Metric</th><th>Value</th></tr><tr><td>Signups</td><td>$SIGNUPS</td></tr></table>" nylas email send --to [email protected] \ --subject "Daily digest $(date +%F)" \ --html "<h1>Daily digest</h1><table><tr><th>Metric</th><th>Value</th></tr><tr><td>Signups</td><td>$SIGNUPS</td></tr></table>" nylas email send --to [email protected] \ --subject "Daily digest $(date +%F)" \ --html "<h1>Daily digest</h1><table><tr><th>Metric</th><th>Value</th></tr><tr><td>Signups</td><td>$SIGNUPS</td></tr></table>" SPARK=$(psql -tA -c "SELECT array_agg(c) FROM (SELECT count(*) c FROM users WHERE created_at > now() - interval '7 days' GROUP BY date_trunc('day', created_at) ORDER BY 1) t" | sed 's/[{}]//g') # Render with https://github.com/holman/spark SPARK_LINE=$(echo $SPARK | tr ',' ' ' | spark) SPARK=$(psql -tA -c "SELECT array_agg(c) FROM (SELECT count(*) c FROM users WHERE created_at > now() - interval '7 days' GROUP BY date_trunc('day', created_at) ORDER BY 1) t" | sed 's/[{}]//g') # Render with https://github.com/holman/spark SPARK_LINE=$(echo $SPARK | tr ',' ' ' | spark) SPARK=$(psql -tA -c "SELECT array_agg(c) FROM (SELECT count(*) c FROM users WHERE created_at > now() - interval '7 days' GROUP BY date_trunc('day', created_at) ORDER BY 1) t" | sed 's/[{}]//g') # Render with https://github.com/holman/spark SPARK_LINE=$(echo $SPARK | tr ',' ' ' | spark) psql -c "\copy (SELECT * FROM yesterday_summary) TO '/tmp/digest.csv' CSV HEADER" nylas email send --to [email protected] \ --subject "Daily digest $(date +%F)" \ --body "Numbers attached." \ --attachment /tmp/digest.csv psql -c "\copy (SELECT * FROM yesterday_summary) TO '/tmp/digest.csv' CSV HEADER" nylas email send --to [email protected] \ --subject "Daily digest $(date +%F)" \ --body "Numbers attached." \ --attachment /tmp/digest.csv psql -c "\copy (SELECT * FROM yesterday_summary) TO '/tmp/digest.csv' CSV HEADER" nylas email send --to [email protected] \ --subject "Daily digest $(date +%F)" \ --body "Numbers attached." \ --attachment /tmp/digest.csv # At top of digest.sh: HOLIDAYS_FILE=/opt/scripts/us-holidays.txt TODAY=$(date +%F) if grep -qx "$TODAY" "$HOLIDAYS_FILE"; then exit 0 fi # At top of digest.sh: HOLIDAYS_FILE=/opt/scripts/us-holidays.txt TODAY=$(date +%F) if grep -qx "$TODAY" "$HOLIDAYS_FILE"; then exit 0 fi # At top of digest.sh: HOLIDAYS_FILE=/opt/scripts/us-holidays.txt TODAY=$(date +%F) if grep -qx "$TODAY" "$HOLIDAYS_FILE"; then exit 0 fi export PATH="$HOME/.config/nylas/bin:$PATH" export PATH="$HOME/.config/nylas/bin:$PATH" export PATH="$HOME/.config/nylas/bin:$PATH" - A Linux box (or macOS) with cron - Whatever query speaks to your data (psql, mysql, an API) - The Nylas CLI installed and authenticated - Per-team digests: loop over a list of recipients with for team in eng product sales; do nylas email send ...; done - On-call alerts: combine with a -weight: 500;">service that emits errors to a queue, send only when count > threshold - Daily metrics from Coralogix / Datadog: replace psql with the provider's CLI or -weight: 500;">curl to their API - Send email from the terminal — full nylas email send reference - PowerShell email reports — same idea on Windows - CI/CD email alerts — build pipeline integrations - Full command reference