- Posted on
- • Artificial Intelligence
Artificial Intelligence Email Productivity
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Turn Your Linux Terminal Into an AI Email Productivity Machine
If you’re drowning in email, you’re not alone. Knowledge workers can spend hours every day reading, triaging, summarizing, and replying to messages. That’s a lot of repetitive, semi-structured text—exactly the kind of work modern AI models are surprisingly good at. The best part? You can run these workflows right from your Linux shell and wire them into the tools you already use.
In this guide, you’ll learn how to:
Summarize unread emails into a daily digest
Draft smart, on-tone replies with one command
Auto-triage messages with tags like action/read_later/archive
Extract tasks and due dates into structured lists (or Taskwarrior)
You’ll get working Bash snippets, package install commands for apt/dnf/zypper, and tips for running models locally (privacy-friendly) or in the cloud (no GPU needed).
Why do this in Bash?
Email is text; Bash is great at text. Combine IMAP sync, indexing, and AI with simple pipelines.
Repeatable and scriptable. Once you trust a workflow, run it from cron, systemd timers, or hotkeys.
Privacy and control. Use a local model with ollama, or a cloud API with explicit redaction rules.
Measurable value. Even a 15–30% reduction in reading and drafting time per day compounds quickly.
Prerequisites and installation
We’ll use standard command-line tools:
IMAP sync: isync (mbsync)
Index and search: notmuch
Utilities: ripgrep (rg), jq, curl
Optional sending: msmtp
Optional local AI: ollama
Optional tasks: Taskwarrior
Install the core tools:
Ubuntu/Debian (apt):
sudo apt update
sudo apt install -y isync notmuch ripgrep jq curl msmtp
Fedora/RHEL/CentOS (dnf):
sudo dnf install -y isync notmuch ripgrep jq curl msmtp
openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y isync notmuch ripgrep jq curl msmtp
Optional: Taskwarrior
- Ubuntu/Debian (apt):
sudo apt install -y taskwarrior
- Fedora/RHEL/CentOS (dnf):
sudo dnf install -y task
- openSUSE (zypper):
sudo zypper install -y taskwarrior
Optional: Local model with ollama (single script installer; not via apt/dnf/zypper)
curl -fsSL https://ollama.com/install.sh | sh
sudo systemctl enable --now ollama
# Pull a model (choose one):
ollama pull llama3:8b
# or
ollama pull mistral:7b
Tip: Local models keep content on your machine. Cloud models are often stronger/lower-latency. You can support both with a switch in your scripts.
Minimal IMAP sync and index
1) Configure isync (mbsync) for your inbox (Maildir). Create ~/.mbsyncrc:
IMAPAccount work
Host imap.example.com
User you@example.com
PassCmd "pass show email/you@example.com"
SSLType IMAPS
IMAPStore work-remote
Account work
MaildirStore work-local
Path ~/.mail/work/
Inbox ~/.mail/work/INBOX
Channel work-inbox
Master :work-remote:
Slave :work-local:
Patterns "INBOX"
Create Both
Expunge Both
SyncState *
2) Run a sync and index:
mbsync work-inbox
notmuch new
Now your messages live in ~/.mail/work and are indexed by notmuch.
Choose your AI runner (cloud or local)
Export credentials for a cloud API (example: OpenAI-compatible endpoint):
export OPENAI_API_KEY="sk-your-key"
export OPENAI_MODEL="gpt-4o-mini" # pick any available compatible model
Or use a local model with ollama:
export OLLAMA_MODEL="llama3:8b" # or mistral:7b, qwen2:7b, etc.
A tiny helper for Bash scripts:
ai_call() {
# Usage: ai_call "your prompt here"
if [ -n "$OPENAI_API_KEY" ]; then
curl -sS https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg m "${OPENAI_MODEL:-gpt-4o-mini}" --arg p "$1" '{
model:$m, temperature:0.2,
messages:[{role:"user",content:$p}]
}')" \
| jq -r '.choices[0].message.content'
else
# Local, via ollama
ollama run "${OLLAMA_MODEL:-llama3:8b}" -p "$1"
fi
}
Note: If you use a different cloud provider, adapt the curl call to their API.
Workflow 1: Daily AI digest of unread emails
Create ai-email-digest.sh:
#!/usr/bin/env bash
set -euo pipefail
LIMIT="${LIMIT:-20}"
QUERY="${QUERY:-tag:unread and tag:inbox}"
# Ensure mailbox is synced and indexed first (optional, uncomment if desired)
# mbsync work-inbox
# notmuch new
EMAILS=$(notmuch show --format=text --entire-thread=false --body=true --exclude=false --limit "$LIMIT" "$QUERY")
PROMPT=$(cat <<'EOF'
You are an assistant that writes a concise daily email digest.
Summarize the following emails into:
- Key highlights (3–7 bullets)
- Action items (who, what, when if stated)
- FYIs
- Risks or deadlines
Rules:
- Be accurate, terse, and specific.
- Include sender and subject snippets when helpful.
- If dates or commitments are mentioned, capture them.
Here are the raw emails delimited by:
=== EMAILS START ===
EOF
)
PROMPT="$PROMPT
=== EMAILS START ===
$EMAILS
=== EMAILS END ==="
ai_call "$PROMPT"
Run it:
chmod +x ai-email-digest.sh
./ai-email-digest.sh > ~/email-digest-$(date +%F).md
Tip: Add to cron/systemd timer each morning.
Workflow 2: One-command smart reply drafts
Draft a reply that’s on-tone and ready for review. Save ai-reply.sh:
#!/usr/bin/env bash
set -euo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $0 <notmuch-query or path-to-.eml>"
exit 1
fi
# Get the message text either from notmuch or a file
if [ -f "$1" ]; then
BODY=$(awk 'BEGIN{h=1} h && /^$/ {h=0;next} !h {print}' "$1")
else
BODY=$(notmuch show --format=text --entire-thread=false --body=true --exclude=false --limit 1 "$1")
fi
STYLE=${STYLE:-"polite, concise, professional"}
SIGNOFF=${SIGNOFF:-"Best regards,\nYour Name"}
INSTRUCTIONS=${INSTRUCTIONS:-"Acknowledge the sender’s main point, answer questions directly, propose next steps if any, keep to 5–10 sentences."}
PROMPT=$(cat <<EOF
Compose a reply email in the following style: $STYLE
Instructions: $INSTRUCTIONS
Do not include extraneous commentary or placeholders. Output only the email body.
If details are missing, ask a brief clarifying question at the end.
Original message:
---
$BODY
---
Include this signoff:
$SIGNOFF
EOF
)
DRAFT=$(ai_call "$PROMPT")
OUT="reply-$(date +%s).txt"
printf "%s\n" "$DRAFT" > "$OUT"
${EDITOR:-nano} "$OUT"
Examples:
./ai-reply.sh 'tag:unread AND from:client@example.com'
# or
./ai-reply.sh ~/Downloads/message.eml
Optional sending (configure msmtp in ~/.config/msmtp/config), then:
msmtp --read-envelope-from --read-recipients < "$OUT"
Workflow 3: Auto-triage with tags (action/read_later/archive/meeting)
Tag messages so search and follow-up are trivial. Save ai-triage.sh:
#!/usr/bin/env bash
set -euo pipefail
LIMIT="${LIMIT:-30}"
QUERY="${QUERY:-tag:unread and tag:inbox}"
# Get JSON with message metadata for better prompts
MSG_JSON=$(notmuch show --format=json --entire-thread=false --body=true --limit "$LIMIT" "$QUERY")
PROMPT=$(cat <<'EOF'
You are labeling emails into exactly one of:
- action (I must do something soon)
- read_later (informational; low priority)
- archive (safe to archive; no action required)
- meeting (scheduling or calendar-related)
Return JSON array of objects: [{id, subject, from, label}], where id is the notmuch message id.
Here are emails in JSON (fields: id, headers, body):
EOF
)
PROMPT="$PROMPT
$MSG_JSON
"
RAW=$(ai_call "$PROMPT")
# Parse and apply tags
echo "$RAW" | jq -cr '.[]' | while read -r row; do
id=$(echo "$row" | jq -r '.id')
label=$(echo "$row" | jq -r '.label' | tr '[:upper:]' '[:lower:]')
case "$label" in
action) notmuch tag +action -unread id:"$id" ;;
read_later) notmuch tag +read_later -unread id:"$id" ;;
archive) notmuch tag +archivable -unread id:"$id" ;;
meeting) notmuch tag +meeting -unread id:"$id" ;;
*) notmuch tag +review -unread id:"$id" ;; # fallback
esac
echo "Tagged $id -> $label"
done
Run:
chmod +x ai-triage.sh
./ai-triage.sh
Now you can quickly list next actions:
notmuch search tag:action
Workflow 4: Extract tasks and due dates into structured data (or Taskwarrior)
Save ai-tasks.sh:
#!/usr/bin/env bash
set -euo pipefail
LIMIT="${LIMIT:-50}"
QUERY="${QUERY:-tag:action or subject:due or subject:deadline or tag:unread}"
TEXT=$(notmuch show --format=text --entire-thread=false --body=true --exclude=false --limit "$LIMIT" "$QUERY")
PROMPT=$(cat <<'EOF'
Extract tasks from the following emails.
Return strict JSON with: tasks: [{description, due_date (YYYY-MM-DD or null), source_hint}]
Rules:
- One task per actionable item.
- Keep descriptions short and imperative.
- Guess due_date only if explicit or strongly implied; else null.
Emails:
===
EOF
)
PROMPT="$PROMPT
$TEXT
==="
JSON=$(ai_call "$PROMPT")
# Save and optionally push into Taskwarrior
echo "$JSON" | jq . > tasks-$(date +%F).json
echo "$JSON" | jq -cr '.tasks[] | [.description, .due_date, .source_hint] | @tsv' | while IFS=$'\t' read -r desc due src; do
if command -v task >/dev/null 2>&1; then
if [ "$due" != "null" ] && [ -n "$due" ]; then
task add "[$src] $desc" due:"$due"
else
task add "[$src] $desc"
fi
else
printf "- %s%s [%s]\n" "$desc" "${due:+ (due $due)}" "$src" >> tasks.md
fi
done
echo "Tasks captured. See tasks.json or tasks.md / Taskwarrior."
Run:
chmod +x ai-tasks.sh
./ai-tasks.sh
Reliability, privacy, and cost tips
Keep prompts deterministic: temperature 0.0–0.3.
Ask for strict JSON; validate with jq before acting.
Redact sensitive content for cloud calls, or prefer a local model for confidential mail.
Batch carefully: long threads can exceed context windows; summarize first, then act.
Log inputs/outputs for a while; revert any mis-tags with notmuch tag -action id:...
Conclusion and next step
Email doesn’t have to be a daily energy drain. With a few packages and small Bash scripts, you can turn AI into a reliable inbox co-pilot: summarize, triage, draft, and extract tasks—all from your terminal.
Your next step:
Install the prerequisites (apt/dnf/zypper above)
Pick a model path (local with ollama or cloud with OPENAI_API_KEY)
Drop the scripts into ~/bin, run them on a small batch, and iterate on prompts
Once you trust the results, schedule them. Tomorrow morning’s inbox can already be lighter.