We build custom applications 5x faster and cheaper 🚀
Book a Free Consultation
Stuck on an error? Book a 30-minute call with an engineer and get a direct fix + next steps. No pressure, no commitment.
A working Email Daily Summary integration with OpenClaw is built as an explicit skill: you implement a small HTTP service (the skill runtime) that knows how to authenticate to the user’s mail provider (OAuth or API key), fetch messages via that provider’s REST API, create a compact summary (local heuristic or call an external summarizer), and deliver the summary by email or webhook. Install that skill through ClawHub, supply the skill’s callback/webhook URL and environment secrets, manage user OAuth flows so you persist refresh tokens in a secure secret store outside the agent, and schedule a daily trigger from an external scheduler or a reliable jobs system that calls the skill. Ensure tokens/scopes are correct, long-running state and scheduling live outside the agent runtime, and use logs + API response checks to debug failures.
https://oauth2.googleapis.com/token, list messages at https://gmail.googleapis.com/gmail/v1/users/me/messages, and fetch message content at https://gmail.googleapis.com/gmail/v1/users/me/messages/{id}?format=full.
// Simple express service: POST /run-summary triggers the work
// Requires environment variables: MAIL_CLIENT_ID, MAIL_CLIENT_SECRET, MAIL_REFRESH_TOKEN, SENDGRID_API_KEY, FROM_EMAIL, TO_EMAIL
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
const {
MAIL_CLIENT_ID,
MAIL_CLIENT_SECRET,
MAIL_REFRESH_TOKEN,
SENDGRID_API_KEY,
FROM_EMAIL,
TO_EMAIL
} = process.env;
async function refreshAccessToken() {
// <b>//</b> OAuth2 token refresh for Google-style token endpoint
const res = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: MAIL_CLIENT_ID,
client_secret: MAIL_CLIENT_SECRET,
refresh_token: MAIL_REFRESH_TOKEN,
grant_type: 'refresh_token'
})
});
if (!res.ok) throw new Error(`token refresh failed ${res.status}`);
return res.json(); // { access_token, expires_in, scope, token_type, ... }
}
async function listMessageIds(accessToken, query = 'newer_than:1d') {
// <b>//</b> list message IDs (Gmail example)
const url = `https://gmail.googleapis.com/gmail/v1/users/me/messages?q=${encodeURIComponent(query)}&maxResults=10`;
const res = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } });
if (!res.ok) throw new Error(`list messages failed ${res.status}`);
const body = await res.json();
return (body.messages || []).map(m => m.id);
}
async function getMessageRaw(accessToken, id) {
// <b>//</b> fetch single message with minimal payload
const url = `https://gmail.googleapis.com/gmail/v1/users/me/messages/${id}?format=full`;
const res = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } });
if (!res.ok) throw new Error(`get message failed ${res.status}`);
return res.json();
}
function extractSimpleSummary(messages) {
// <b>//</b> Very simple extractive summary: subject + first sentence of snippet
const lines = messages.map(m => {
const subjHeader = (m.payload && m.payload.headers || []).find(h => h.name === 'Subject');
const subject = subjHeader ? subjHeader.value : '(no subject)';
const snippet = m.snippet || '';
const firstSentence = snippet.split(/(?<=[.!?])\s/)[0];
return `- ${subject}: ${firstSentence}`;
});
return lines.join('\n');
}
async function sendEmail(summary) {
// <b>//</b> Send using SendGrid v3 API
const res = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${SENDGRID_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
personalizations: [{ to: [{ email: TO_EMAIL }], subject: 'Daily Email Summary' }],
from: { email: FROM_EMAIL },
content: [{ type: 'text/plain', value: summary }]
})
});
if (!res.ok) {
const txt = await res.text();
throw new Error(`send email failed ${res.status} - ${txt}`);
}
}
app.post('/run-summary', async (req, res) => {
try {
// <b>//</b> refresh token -> access token
const token = await refreshAccessToken();
const accessToken = token.access_token;
// <b>//</b> list message ids and fetch a few messages
const ids = await listMessageIds(accessToken, 'newer_than:1d');
const fetches = ids.slice(0, 5).map(id => getMessageRaw(accessToken, id));
const messages = await Promise.all(fetches);
// <b>//</b> summarize and send
const summary = extractSimpleSummary(messages);
await sendEmail(summary);
res.json({ ok: true, summaryCount: messages.length });
} catch (err) {
console.error('run-summary error', err);
res.status(500).json({ ok: false, error: String(err) });
}
});
const port = process.env.PORT || 3000;
app.listen(port, () => console.log('skill listening', port));
Speak one‑on‑one with a senior engineer about your no‑code app, migration goals, and budget. In just half an hour you’ll leave with clear, actionable next steps—no strings attached.
1
The scheduler usually fails to create digests because its trigger or permissions are misconfigured, the Email Daily Summary skill isn’t installed/authorized in ClawHub, required environment credentials (SMTP/API keys or pipeline write creds) are missing, or the Digest Pipeline consumer/worker is not running. Check scheduler cron/timezone, skill registration, auth, and runtime logs first.
2
The Email Daily Summary fails because the SMTP connector is rejecting authentication: the agent/runtime either has wrong or missing SMTP credentials, the mail service requires a different auth method (OAuth/app‑password), or the runtime cannot read the secret (env var or ClawHub credential) due to misconfiguration or permission scoping.
3
The templates aren’t seeing the enriched fields because the enrichment step isn’t present in the notification render context or it runs after template rendering. Common causes: enrichment skill failed or lacked creds/permission, enrichment output keys don’t match the template paths, or the notification engine uses a different template context/engine.
4
Direct answer: Events are missing because the OpenClaw Rule Engine may be matching and dropping them before they reach the Daily Summary, or the Event Stream schema validation is rejecting events (invalid fields/types), so the summary skill never receives them.
Rule Engine applies configured rules that match attributes and either route, modify, or drop events. A miswritten condition, wrong field path, or priority causes unintentional drops.
If an event fails the Event Stream JSON schema (missing required fields, wrong types) it’s rejected. Validation errors appear in ingest logs and prevent downstream skills from seeing the event.
This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â