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
$ -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