Complete email infrastructure and automated alert system for the Tutor Quality Dashboard.
This system consists of:
- Email Infrastructure - Resend-based email delivery with tracking
- Email Templates - React-based email templates for various intervention types
- Alert Rules Engine - Configurable rules for detecting tutor issues
- Alert Generation - Automated alert creation based on tutor metrics
- Email Scheduler - Automated delivery of intervention emails
npm install resend react-email @react-email/components @react-email/renderAdd to your .env file:
# Email (Resend)
RESEND_API_KEY="re_xxxxxxxxxxxxxxxxxxxxxxxxxx"
EMAIL_FROM="Tutor Success <noreply@tutorquality.com>"
# Application
NEXT_PUBLIC_APP_URL="http://localhost:3000"
# Cron Secret (for production)
CRON_SECRET="your-secret-key-here"- Sign up at resend.com
- Create an API key
- Verify your domain (or use sandbox for testing)
- Engagement Alert - No login or session activity
- First Session Reminder - Upcoming first session preparation
- Quality Alert - Low ratings, engagement, empathy, or clarity
- Technical Issues - Connection or platform problems
- Re-engagement - Long-term inactive tutors
View all email templates in your browser:
npm run dev
# Then visit:
http://localhost:3000/api/email-preview?template=engagement
http://localhost:3000/api/email-preview?template=first-session
http://localhost:3000/api/email-preview?template=quality
http://localhost:3000/api/email-preview?template=technical
http://localhost:3000/api/email-preview?template=re-engagement- Create a new file in
lib/email/templates/ - Use the
BaseEmailLayoutcomponent - Add to
email-preview/route.tsfor testing
import BaseEmailLayout from './base-layout';
export default function MyNewEmail({ tutorName, ...props }) {
return (
<BaseEmailLayout
preview="Your preview text"
tutorName={tutorName}
>
<Text style={paragraph}>Your email content</Text>
</BaseEmailLayout>
);
}The system includes 12 pre-configured alert rules:
- High Churn Risk (Priority 100) - Tutor classified as high churn risk
- No Sessions in 14 Days (Priority 95) - No tutoring activity for 2 weeks
- No Login in 7 Days (Priority 90) - Account inactivity
- Poor First Session (Priority 85) - First session ratings below 4.0
- Declining Engagement Trend (Priority 80) - Week-over-week decline
- Low Student Rating (Priority 75) - 7-day average below 4.0
- Technical Issues Spike (Priority 65) - Issue rate above 12%
- High Reschedule Rate (Priority 60) - Reschedule rate above 15%
- Low Engagement Score (Priority 55) - Engagement below 5.5
- Low Empathy Score (Priority 50) - Empathy below 5.0
- Low Clarity Score (Priority 50) - Clarity below 5.5
- First Session Scheduled (Priority 20) - Upcoming first session
Edit lib/alerts/rules.ts:
{
id: 'my_custom_rule',
name: 'My Custom Rule',
description: 'Description of what triggers this rule',
category: 'quality', // churn, quality, technical, engagement
severity: 'high', // critical, high, medium, low
priority: 70, // Higher = checked first
condition: (context) => {
// Return true when alert should be triggered
return context.tutor.aggregates?.someMetric < threshold;
},
generateMessage: (context) => ({
title: 'Alert Title',
message: 'Detailed alert message',
metric: 'metric_name',
metricValue: context.tutor.aggregates?.someMetric,
threshold: 5.0,
}),
}Run the alert generation script manually:
npm run generate-alertsThis will:
- Fetch all active tutors
- Evaluate alert rules for each tutor
- Create alerts in the database (with deduplication)
- Auto-resolve alerts older than 30 days
Configure Vercel Cron jobs in vercel.json:
{
"crons": [
{
"path": "/api/cron/generate-alerts",
"schedule": "0 * * * *"
}
]
}This runs hourly in production.
# Get all unresolved alerts
GET /api/alerts
# Filter by tutor
GET /api/alerts?tutorId=T0001
# Filter by severity
GET /api/alerts?severity=critical
# Filter by category
GET /api/alerts?category=churn
# Include resolved alerts
GET /api/alerts?includeResolved=truePOST /api/alerts
Content-Type: application/json
{
"tutorId": "T0001",
"severity": "high",
"category": "quality",
"title": "Custom Alert",
"message": "Alert description",
"metric": "custom_metric",
"metricValue": 3.5,
"threshold": 5.0
}PATCH /api/alerts
Content-Type: application/json
{
"alertId": "alert-id-here",
"action": "acknowledge",
"acknowledgedBy": "manager@example.com"
}PATCH /api/alerts
Content-Type: application/json
{
"alertId": "alert-id-here",
"action": "resolve"
}GET /api/alerts/statsReturns:
{
"totalAlerts": 45,
"unacknowledged": 23,
"byCategory": [
{ "category": "churn", "count": 12 },
{ "category": "quality", "count": 20 },
{ "category": "technical", "count": 8 },
{ "category": "engagement", "count": 5 }
],
"bySeverity": [
{ "severity": "critical", "count": 5 },
{ "severity": "high", "count": 15 },
{ "severity": "medium", "count": 20 },
{ "severity": "low", "count": 5 }
]
}Send pending intervention emails manually:
npm run send-emailsThis will:
- Fetch pending interventions (status = 'pending')
- Check for unsubscribes
- Generate appropriate email templates
- Send emails via Resend
- Update intervention status
- Rate limit to 10 emails/second
Configure Vercel Cron jobs in vercel.json:
{
"crons": [
{
"path": "/api/cron/send-emails",
"schedule": "0 */6 * * *"
}
]
}This runs every 6 hours in production.
Interventions can be created from alerts or manually:
// From an alert
import { createInterventionFromAlert } from '@/lib/email/scheduler';
const interventionId = await createInterventionFromAlert(alertId);
// Manual creation
await prisma.intervention.create({
data: {
tutorId: 'T0001',
interventionType: 'engagement',
channel: 'email',
subject: 'We Miss You!',
content: 'Email content here',
status: 'pending',
},
});The system automatically tracks:
- Sent - Email delivered successfully
- Opened - Recipient opened the email (via tracking pixel)
- Clicked - Recipient clicked a link (via tracked URLs)
- Responded - Tutor took action (manual update)
Track metrics in the interventions table:
sentAt- When email was sentopenedAt- When email was openedclickedAt- When link was clickedrespondedAt- When tutor responded
// In a test script
import { sendEmail } from '@/lib/email/sender';
import EngagementAlertEmail from '@/lib/email/templates/engagement-alert-email';
await sendEmail({
to: 'test@example.com',
subject: 'Test Email',
template: EngagementAlertEmail({
tutorName: 'Test Tutor',
issueType: 'no_login',
daysSinceLastLogin: 7,
actionUrl: 'http://localhost:3000/dashboard',
}),
});import { evaluateAlertRules } from '@/lib/alerts/rules';
const { triggeredRules, context } = await evaluateAlertRules('T0001');
console.log(`Triggered ${triggeredRules.length} alerts:`);
triggeredRules.forEach(rule => {
console.log(`- ${rule.message.title}: ${rule.message.message}`);
});Check Resend dashboard for:
- Delivery rates
- Open rates
- Click rates
- Bounce rates
- Spam complaints
Monitor cron job logs in Vercel:
vercel logs --follow-- Check alert distribution
SELECT category, severity, COUNT(*) as count
FROM alerts
WHERE is_resolved = false
GROUP BY category, severity
ORDER BY severity DESC, category;
-- Check intervention status
SELECT status, COUNT(*) as count
FROM interventions
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY status;
-- Top tutors with most alerts
SELECT tutor_id, COUNT(*) as alert_count
FROM alerts
WHERE is_resolved = false
GROUP BY tutor_id
ORDER BY alert_count DESC
LIMIT 10;- Environment Variables - Set in Vercel dashboard
- Cron Jobs - Auto-configured via
vercel.json - Domain Verification - Verify email domain in Resend
- Cron endpoints protected with
CRON_SECRET - Email tracking uses secure tokens
- Unsubscribe links use encrypted tutor IDs
- Email sending: 10 emails/second (configurable)
- Alert generation: Batch processing (20 tutors at a time)
- API endpoints: Consider adding Upstash rate limiting
- Check Resend API key is set
- Verify domain in Resend dashboard
- Check intervention status in database
- Review Vercel logs for errors
- Ensure tutors have aggregates data
- Check alert rules are triggering
- Verify deduplication isn't blocking
- Review tutor metrics vs thresholds
- Verify
vercel.jsonis deployed - Check Vercel dashboard > Cron Jobs
- Ensure
CRON_SECRETis set - Review Vercel function logs
- SMS notifications via Twilio
- In-app notifications
- Email A/B testing
- Advanced segmentation
- Machine learning alert scoring
- Webhook integrations
- Analytics dashboard
- Custom email builder UI
See inline documentation in:
lib/email/sender.ts- Email sending functionslib/email/scheduler.ts- Email scheduling functionslib/alerts/rules.ts- Alert rule definitionslib/alerts/generator.ts- Alert generation functions