| name | email-digest |
| version | 1.0 |
| description | Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer. |
| tools | ["Bash","Read","Write","Edit"] |
email-digest skill
The reading-list digest sends a daily email summarizing unread articles. It is delivered via nodemailer using SMTP credentials. The digest is scheduled by a setInterval loop that runs every minute and checks whether the configured delivery time has been reached.
SMTP configuration
Set these environment variables before starting the server:
DIGEST_SMTP_HOST=smtp.gmail.com
DIGEST_SMTP_PORT=587
DIGEST_SMTP_USER=you@gmail.com
DIGEST_SMTP_PASS=<app-specific-password>
DIGEST_FROM="Reading List <you@gmail.com>"
Gmail setup
Gmail requires an App Password when 2FA is enabled:
- Go to https://myaccount.google.com/security
- Under "How you sign in to Google", open "2-Step Verification"
- Scroll to "App passwords" and generate one for "Mail"
- Use the generated 16-character password as
DIGEST_SMTP_PASS
Self-hosted SMTP (Postfix / local)
DIGEST_SMTP_HOST=localhost
DIGEST_SMTP_PORT=25
DIGEST_SMTP_USER=
DIGEST_SMTP_PASS=
Leave USER and PASS empty if your local MTA does not require auth.
Mailhog (development testing)
Run Mailhog locally to catch emails without sending them:
docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog
Then set:
DIGEST_SMTP_HOST=localhost
DIGEST_SMTP_PORT=1025
DIGEST_SMTP_USER=
DIGEST_SMTP_PASS=
Open http://localhost:8025 to view captured emails.
Digest API
Get configuration
curl http://localhost:3000/api/digest/config
Response:
{
"enabled": true,
"deliveryTime": "08:00",
"email": "you@example.com",
"minWordCount": 0,
"maxArticles": 10
}
Update configuration
curl -X PATCH http://localhost:3000/api/digest/config \
-H 'Content-Type: application/json' \
-d '{
"enabled": true,
"deliveryTime": "08:00",
"email": "you@example.com",
"minWordCount": 300,
"maxArticles": 10
}'
deliveryTime: 24-hour HH:MM format in UTC.
minWordCount: Only include articles with at least this many words. Set to 0 to include all.
maxArticles: Maximum articles included per digest. Sorted by newest first.
Send a digest immediately (for testing)
curl -X POST http://localhost:3000/api/digest/send
This bypasses the schedule and sends a digest right now to the configured email. Useful for verifying SMTP config.
View sent history
curl http://localhost:3000/api/digest/history
Returns an array of past digest runs:
[
{ "id": "dgs_1", "sentAt": "2026-03-20T08:00:00Z", "articleCount": 10, "status": "sent" },
{ "id": "dgs_2", "sentAt": "2026-03-19T08:00:00Z", "articleCount": 7, "status": "sent" },
{ "id": "dgs_3", "sentAt": "2026-03-18T08:00:00Z", "articleCount": 0, "status": "skipped" }
]
Status values:
| Status | Meaning |
|---|
sent | Digest was sent successfully |
skipped | No unread articles met the criteria; nothing was sent |
failed | SMTP error; check server logs |
Digest email format
The digest email is an HTML email containing:
- Subject:
Reading List Digest - March 20
- Header: count of articles and total estimated reading time
- Article list: thumbnail placeholder, title, site name, excerpt, read time, tag chips, and a "Read Now" link
- Footer: unsubscribe link that calls
PATCH /api/digest/config with { "enabled": false }
Troubleshooting
No digest received
- Check that
enabled is true in the digest config.
- Verify the configured email address is correct.
- Trigger a manual send:
POST /api/digest/send.
- Check the SMTP credentials by sending a test email:
node -e "
const nodemailer = require('nodemailer');
const t = nodemailer.createTransport({
host: process.env.DIGEST_SMTP_HOST,
port: parseInt(process.env.DIGEST_SMTP_PORT || '587'),
auth: { user: process.env.DIGEST_SMTP_USER, pass: process.env.DIGEST_SMTP_PASS }
});
t.verify().then(() => console.log('SMTP OK')).catch(e => console.error(e.message));
"
Digest sent but emails not arriving
- Check spam folder.
- For Gmail, ensure the App Password is correct and 2FA is active.
- Check
digest_history table for status='failed' rows; the error_msg column contains the SMTP error.
Digest sends at wrong time
The scheduler compares the current UTC time to deliveryTime. If your server is in a non-UTC timezone, convert your local time to UTC. For example, if you want delivery at 8am UTC+2, set deliveryTime to 06:00.
Digest skipped despite having unread articles
The digest is only sent if at least one article passes the minWordCount filter. Lower the threshold or set it to 0.