Overview
Better Uptime sends email notifications to keep you informed about your monitor status changes. The notification system uses Resend for reliable email delivery.
Email Notifications via Resend
Better Uptime uses Resend as the email delivery service.
Email Configuration
packages/api/src/email.ts
import { Resend } from "resend" ;
import { RESEND_API_KEY } from "@repo/config" ;
import { FRONTEND_URL } from "@repo/config/constants" ;
const resend = new Resend ( RESEND_API_KEY );
Sending Verification Emails
The primary notification function sends email verification links:
packages/api/src/email.ts
export async function sendVerificationEmail (
email : string ,
token : string ,
) : Promise <{ success : boolean ; error ?: string }> {
const verificationUrl = ` ${ FRONTEND_URL } /auth/verify-email?token= ${ token } ` ;
try {
const { error } = await resend . emails . send ({
from: "[email protected] " ,
to: email ,
subject: "Verify your email address" ,
html: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 40px 20px; background-color: #f9fafb;">
<div style="max-width: 480px; margin: 0 auto; background: white; border-radius: 8px; padding: 40px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h1 style="margin: 0 0 24px; font-size: 24px; font-weight: 600; color: #111827;">
Verify your email
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 1.5; color: #4b5563;">
Thanks for signing up at <b>rshdhere technologies</b>! Please click the button below to verify your email address.
</p>
<a href=" ${ verificationUrl } " style="display: inline-block; padding: 12px 24px; background-color: #111827; color: white; text-decoration: none; border-radius: 6px; font-weight: 500; font-size: 14px;">
Verify Email
</a>
<p style="margin: 24px 0 0; font-size: 14px; line-height: 1.5; color: #6b7280;">
If you didn't create an account, you can safely ignore this email.
</p>
<p style="margin: 16px 0 0; font-size: 12px; color: #9ca3af;">
This link will expire in 24 hours.
</p>
</div>
</body>
</html>
` ,
});
if ( error ) {
console . error ( "Failed to send verification email:" , error );
return { success: false , error: error . message };
}
return { success: true };
} catch ( err ) {
console . error ( "Error sending verification email:" , err );
return {
success: false ,
error: err instanceof Error ? err . message : "Failed to send email" ,
};
}
}
Email Template Design
The email templates use inline styles for broad email client compatibility:
Design Principles
Responsive Max-width container adapts to mobile and desktop
Accessible Semantic HTML with proper meta tags
Clean Design Minimal, professional styling with clear CTAs
System Fonts Uses system font stack for fast rendering
Template Structure
<!-- Container -->
< body style = "font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 40px 20px; background-color: #f9fafb;" >
<!-- Card -->
< div style = "max-width: 480px; margin: 0 auto; background: white; border-radius: 8px; padding: 40px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);" >
<!-- Heading -->
< h1 style = "margin: 0 0 24px; font-size: 24px; font-weight: 600; color: #111827;" >
Verify your email
</ h1 >
<!-- Body Text -->
< p style = "margin: 0 0 24px; font-size: 16px; line-height: 1.5; color: #4b5563;" >
Thanks for signing up at < b > rshdhere technologies </ b > ! Please click the button below to verify your email address.
</ p >
<!-- CTA Button -->
< a href = "${verificationUrl}" style = "display: inline-block; padding: 12px 24px; background-color: #111827; color: white; text-decoration: none; border-radius: 6px; font-weight: 500; font-size: 14px;" >
Verify Email
</ a >
<!-- Footer -->
< p style = "margin: 24px 0 0; font-size: 14px; line-height: 1.5; color: #6b7280;" >
If you didn't create an account, you can safely ignore this email.
</ p >
<!-- Expiration Notice -->
< p style = "margin: 16px 0 0; font-size: 12px; color: #9ca3af;" >
This link will expire in 24 hours.
</ p >
</ div >
</ body >
Notification Triggers
Notifications are triggered based on monitor status changes detected by the worker service.
Status Change Detection
Workers continuously monitor websites and detect status changes:
async function checkWebsite (
url : string ,
websiteId : string ,
) : Promise < UptimeEventRecord > {
const startTime = Date . now ();
let status : UptimeStatus = "DOWN" ;
let httpStatus : number | undefined ;
try {
const res = await axios . get ( url , {
maxRedirects: 5 ,
validateStatus : () => true ,
headers: {
"User-Agent" :
"Uptique/1.0 (Uptime Monitor; https://uptique.raashed.xyz)" ,
},
});
httpStatus = res . status ;
status = typeof httpStatus === "number" && httpStatus < 500 ? "UP" : "DOWN" ;
} catch ( error ) {
// Network error - status remains DOWN
}
return {
websiteId ,
regionId: REGION_ID ,
status ,
responseTimeMs: Date . now () - startTime ,
httpStatusCode: httpStatus ,
checkedAt: new Date (),
};
}
Notification Logic
Notifications should be triggered when:
Monitor goes DOWN : HTTP status ≥ 500 or network error
Monitor recovers (UP) : HTTP status < 500 after being DOWN
Sustained outage : Monitor remains DOWN for multiple checks
Currently, the codebase includes email verification functionality. Status change notifications would be implemented by comparing previous and current status in the worker’s processing loop.
Implementing Status Notifications
To add status change notifications, you would extend the email service:
// Proposed implementation
export async function sendDowntimeAlert (
email : string ,
website : { name : string ; url : string },
incident : { status : string ; httpStatusCode ?: number ; responseTimeMs ?: number }
) : Promise <{ success : boolean ; error ?: string }> {
const dashboardUrl = ` ${ FRONTEND_URL } /dashboard/incidents` ;
try {
const { error } = await resend . emails . send ({
from: "[email protected] " ,
to: email ,
subject: `🚨 ${ website . name } is DOWN` ,
html: `
<!DOCTYPE html>
<html>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 40px 20px; background-color: #f9fafb;">
<div style="max-width: 480px; margin: 0 auto; background: white; border-radius: 8px; padding: 40px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h1 style="margin: 0 0 24px; font-size: 24px; font-weight: 600; color: #dc2626;">
⚠️ ${ website . name } is DOWN
</h1>
<p style="margin: 0 0 16px; font-size: 16px; line-height: 1.5; color: #4b5563;">
Your monitor <b> ${ website . name } </b> is currently experiencing downtime.
</p>
<div style="background: #fef2f2; border: 1px solid #fee2e2; border-radius: 6px; padding: 16px; margin: 0 0 24px;">
<p style="margin: 0 0 8px; font-size: 14px; color: #991b1b;"><b>URL:</b> ${ website . url } </p>
<p style="margin: 0 0 8px; font-size: 14px; color: #991b1b;"><b>Status:</b> ${ incident . status } </p>
${ incident . httpStatusCode ? `<p style="margin: 0; font-size: 14px; color: #991b1b;"><b>HTTP Status:</b> ${ incident . httpStatusCode } </p>` : '' }
</div>
<a href=" ${ dashboardUrl } " style="display: inline-block; padding: 12px 24px; background-color: #dc2626; color: white; text-decoration: none; border-radius: 6px; font-weight: 500; font-size: 14px;">
View Dashboard
</a>
</div>
</body>
</html>
` ,
});
if ( error ) {
console . error ( "Failed to send downtime alert:" , error );
return { success: false , error: error . message };
}
return { success: true };
} catch ( err ) {
console . error ( "Error sending downtime alert:" , err );
return {
success: false ,
error: err instanceof Error ? err . message : "Failed to send email" ,
};
}
}
Error Handling
The email service includes comprehensive error handling:
packages/api/src/email.ts
try {
const { error } = await resend . emails . send ({ ... });
if ( error ) {
console . error ( "Failed to send verification email:" , error );
return { success: false , error: error . message };
}
return { success: true };
} catch ( err ) {
console . error ( "Error sending verification email:" , err );
return {
success: false ,
error: err instanceof Error ? err . message : "Failed to send email" ,
};
}
Email failures are logged but don’t crash the application. The system returns detailed error messages for debugging.
Email Configuration
Environment Variables
RESEND_API_KEY = re_xxxxxxxxxxxxx
FRONTEND_URL = https://yourapp.com
Required Configuration
Base URL for your application (used in email links)
Best Practices
Improve deliverability:
Verify your sending domain in Resend
Set up SPF, DKIM, and DMARC records
Use a recognizable “from” name
Include unsubscribe links for transactional emails
Prevent email spam:
Implement cooldown periods between alerts
Batch multiple status changes into digest emails
Allow users to configure notification frequency
Respect user notification preferences
Test email templates:
Use Litmus or Email on Acid
Test in Gmail, Outlook, Apple Mail
Verify mobile responsiveness
Check dark mode rendering
Track email delivery:
Log all email send attempts
Monitor Resend webhook events
Track bounce and complaint rates
Alert on high failure rates
Notification Types
Verification Emails Sent when users sign up to verify their email address
Downtime Alerts (To be implemented) Notify when monitors go DOWN
Recovery Notifications (To be implemented) Notify when monitors recover
Incident Reports (To be implemented) Summarize incidents with metrics
Testing Emails Locally
During development, use Resend’s test mode:
// For testing, send to your own email
if ( process . env . NODE_ENV === 'development' ) {
recipient = process . env . DEV_TEST_EMAIL || email ;
}
await resend . emails . send ({
from: "[email protected] " ,
to: recipient ,
subject: "[TEST] Verify your email address" ,
// ...
});
Resend provides a generous free tier with 3,000 emails per month, perfect for development and small production workloads.
Uptime Monitoring Learn how the monitoring system detects downtime
Status Pages Share status publicly with your users
Resend Docs Official Resend API documentation
Email Best Practices Resend’s guide to email deliverability