Skip to main content

Overview

Better Uptime sends email notifications to keep you informed about your monitor status changes. The notification system uses Resend for reliable email delivery.
All email notifications are sent from [email protected] using Resend’s email API.

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:
apps/worker/src/index.ts
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:
  1. Monitor goes DOWN: HTTP status ≥ 500 or network error
  2. Monitor recovers (UP): HTTP status < 500 after being DOWN
  3. 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

RESEND_API_KEY
string
required
Your Resend API key from https://resend.com/api-keys
FRONTEND_URL
string
required
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

Build docs developers (and LLMs) love