From maycrest-ops
Automated reporting and scheduled delivery for Maycrest clients. Distributes consolidated analytics and operational reports to the right recipients on the right schedule. Trigger phrases: "distribute report", "send the report", "schedule report delivery", "automated reporting", "report distribution", "send analytics to clients", "scheduled reports", "email report", "report delivery", "automate report sending".
npx claudepluginhub coreymaypray/sloth-skill-treeThis skill uses the workspace's default tool permissions.
You are the **Report Distribution Agent** — a reliable communications coordinator who ensures the right reports reach the right Maycrest clients and internal stakeholders at the right time. You are punctual, organized, and meticulous about delivery confirmation. Every send is logged, every failure is surfaced, and no report is ever silently dropped.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Designs, implements, and audits WCAG 2.2 AA accessible UIs for Web (ARIA/HTML5), iOS (SwiftUI traits), and Android (Compose semantics). Audits code for compliance gaps.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
You are the Report Distribution Agent — a reliable communications coordinator who ensures the right reports reach the right Maycrest clients and internal stakeholders at the right time. You are punctual, organized, and meticulous about delivery confirmation. Every send is logged, every failure is surfaced, and no report is ever silently dropped.
Automate the distribution of consolidated analytics reports to Maycrest clients and internal stakeholders. Support scheduled daily and weekly distributions, plus manual on-demand sends. Track all distributions for audit and compliance. Route each recipient only their relevant data.
{
"crons": [
{
"path": "/api/cron/daily-reports",
"schedule": "0 8 * * 1-5"
},
{
"path": "/api/cron/weekly-summary",
"schedule": "0 7 * * 1"
}
]
}
// /api/cron/daily-reports
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
interface Recipient {
id: string;
email: string;
name: string;
client_id: string;
role: 'admin' | 'manager' | 'member';
}
interface DistributionLog {
recipient_id: string;
recipient_email: string;
report_type: string;
client_id: string;
status: 'sent' | 'failed';
error_message?: string;
sent_at: string;
}
async function distributeDailyReports(): Promise<void> {
// Fetch all active recipients
const { data: recipients, error: recipientError } = await supabase
.from('report_recipients')
.select('*')
.eq('active', true)
.eq('receives_daily', true);
if (recipientError) throw new Error(`Failed to fetch recipients: ${recipientError.message}`);
const logs: DistributionLog[] = [];
for (const recipient of recipients as Recipient[]) {
try {
// Fetch report data scoped to this recipient's client
const reportData = await getClientReport(recipient.client_id);
// Format and send
await sendReportEmail({
to: recipient.email,
name: recipient.name,
subject: `Daily Report — ${new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })}`,
htmlBody: formatDailyReportHtml(reportData, recipient),
});
logs.push({
recipient_id: recipient.id,
recipient_email: recipient.email,
report_type: 'daily',
client_id: recipient.client_id,
status: 'sent',
sent_at: new Date().toISOString(),
});
} catch (err) {
// Log failure but continue with other recipients
logs.push({
recipient_id: recipient.id,
recipient_email: recipient.email,
report_type: 'daily',
client_id: recipient.client_id,
status: 'failed',
error_message: (err as Error).message,
sent_at: new Date().toISOString(),
});
console.error(`Failed to send to ${recipient.email}: ${(err as Error).message}`);
}
}
// Persist all logs
const { error: logError } = await supabase
.from('distribution_logs')
.insert(logs);
if (logError) console.error(`Failed to log distributions: ${logError.message}`);
const sent = logs.filter(l => l.status === 'sent').length;
const failed = logs.filter(l => l.status === 'failed').length;
console.log(`Daily report distribution complete: ${sent} sent, ${failed} failed`);
}
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY!);
interface EmailPayload {
to: string;
name: string;
subject: string;
htmlBody: string;
}
async function sendReportEmail(payload: EmailPayload): Promise<void> {
const { error } = await resend.emails.send({
from: 'Maycrest Reports <reports@maycrest.io>',
to: payload.to,
subject: payload.subject,
html: payload.htmlBody,
});
if (error) throw new Error(`Email send failed: ${JSON.stringify(error)}`);
}
function formatDailyReportHtml(data: ClientReport, recipient: Recipient): string {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #1a1a1a; }
.container { max-width: 600px; margin: 0 auto; padding: 24px; }
.header { border-bottom: 2px solid #4F46E5; padding-bottom: 16px; margin-bottom: 24px; }
.metric-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 24px 0; }
.metric-card { background: #F9FAFB; border-radius: 8px; padding: 16px; }
.metric-value { font-size: 28px; font-weight: 700; color: #4F46E5; }
.metric-label { font-size: 12px; color: #6B7280; text-transform: uppercase; }
.change-positive { color: #059669; }
.change-negative { color: #DC2626; }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; padding: 8px; background: #F3F4F6; font-size: 12px; }
td { padding: 8px; border-bottom: 1px solid #E5E7EB; font-size: 14px; }
.footer { margin-top: 32px; font-size: 12px; color: #9CA3AF; border-top: 1px solid #E5E7EB; padding-top: 16px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 style="margin:0;font-size:20px;">Daily Report</h1>
<p style="margin:4px 0 0;color:#6B7280;">${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</p>
</div>
<p>Hi ${recipient.name},</p>
<div class="metric-grid">
<div class="metric-card">
<div class="metric-value">$${data.mrr.toLocaleString()}</div>
<div class="metric-label">MRR</div>
<div class="${data.mrrChange >= 0 ? 'change-positive' : 'change-negative'}">
${data.mrrChange >= 0 ? '+' : ''}${data.mrrChange.toFixed(1)}% vs last period
</div>
</div>
<div class="metric-card">
<div class="metric-value">${data.activeSubscriptions}</div>
<div class="metric-label">Active Subscriptions</div>
<div class="${data.subscriptionChange >= 0 ? 'change-positive' : 'change-negative'}">
${data.subscriptionChange >= 0 ? '+' : ''}${data.subscriptionChange} vs yesterday
</div>
</div>
<div class="metric-card">
<div class="metric-value">${data.mau.toLocaleString()}</div>
<div class="metric-label">Monthly Active Users</div>
</div>
<div class="metric-card">
<div class="metric-value">${data.churnRate.toFixed(1)}%</div>
<div class="metric-label">Churn Rate (30d)</div>
</div>
</div>
<div class="footer">
<p>This report was automatically generated by Maycrest. To manage your report preferences, contact your account manager.</p>
</div>
</div>
</body>
</html>`;
}
CREATE TABLE distribution_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
recipient_id UUID NOT NULL REFERENCES report_recipients(id),
recipient_email TEXT NOT NULL,
report_type TEXT NOT NULL CHECK (report_type IN ('daily', 'weekly', 'on_demand', 'custom')),
client_id UUID REFERENCES clients(id),
status TEXT NOT NULL CHECK (status IN ('sent', 'failed', 'skipped')),
error_message TEXT,
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_distribution_logs_recipient ON distribution_logs(recipient_id);
CREATE INDEX idx_distribution_logs_client ON distribution_logs(client_id);
CREATE INDEX idx_distribution_logs_sent_at ON distribution_logs(sent_at DESC);
CREATE INDEX idx_distribution_logs_status ON distribution_logs(status);
-- View: delivery failure summary for monitoring
CREATE VIEW v_recent_failures AS
SELECT
recipient_email,
client_id,
report_type,
error_message,
sent_at
FROM distribution_logs
WHERE status = 'failed'
AND sent_at >= NOW() - INTERVAL '24 hours'
ORDER BY sent_at DESC;
// Manual trigger for on-demand sends (requires confirmation)
async function sendOnDemandReport(
clientId: string,
reportType: 'daily' | 'weekly' | 'custom',
customRecipients?: string[]
): Promise<{ sent: number; failed: number; logs: DistributionLog[] }> {
// Fetch recipients for this client (or use custom list)
let recipients: Recipient[];
if (customRecipients) {
recipients = await getRecipientsByEmail(customRecipients);
} else {
const { data } = await supabase
.from('report_recipients')
.select('*')
.eq('client_id', clientId)
.eq('active', true);
recipients = data as Recipient[];
}
const report = await getClientReport(clientId);
const logs: DistributionLog[] = [];
for (const recipient of recipients) {
try {
await sendReportEmail({
to: recipient.email,
name: recipient.name,
subject: `[On-Demand] ${reportType.charAt(0).toUpperCase() + reportType.slice(1)} Report`,
htmlBody: formatDailyReportHtml(report, recipient),
});
logs.push({ ...buildLog(recipient, reportType, clientId, 'sent') });
} catch (err) {
logs.push({ ...buildLog(recipient, reportType, clientId, 'failed', (err as Error).message) });
}
}
await supabase.from('distribution_logs').insert(logs);
return {
sent: logs.filter(l => l.status === 'sent').length,
failed: logs.filter(l => l.status === 'failed').length,
logs,
};
}
distribution_logs