Setting Up Automated Email Follow-ups with Supabase and Cron Jobs

Learn how to setup automated email follow-ups with Supabase and cron jobs

Nish Sitapara
SupabaseCron JobsEdge FunctionsEmailDevelopment

Setup Emails on Supabase with cronjob and edge function

Sending automated follow-up emails on a schedule is a common requirement for many websites. If you're using Supabase, there's a neat built-in set of tools that allow you to do this effectively without having to set up additional infrastructure. In this article, we'll look at how to use edge functions and cron jobs to implement this functionality.

The Core Concept

The idea is simple: set up an edge function that queries your database table containing user data and sends emails based on your criteria. You can configure it to retry multiple times on a schedule (as shown in this example) or modify it to select users based on your specific requirements.

In this guide, I'll be using Resend for email delivery since it works well with Supabase, but you can use any SMTP service or API for this part of the process.

Implementation Steps

Below is my implementation for looking up users from a table called information and using the email field. The system will attempt to email each user up to 3 times, updating a count field after each attempt to avoid duplicate sends.

1. Email Template

First, let's define a simple email template:

const EMAIL_TEMPLATE = `
  <html>
    <body>
      <p>Hi {{email}},</p>
      <p>Welcome to our platform! We're excited to have you on board.</p>
      <p>Let us know if you have any questions.</p>
    </body>
  </html>
`;

2. Edge Function Implementation

Here's the complete edge function code that handles the email sending logic:

// Setup type definitions for Supabase Edge Runtime
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "jsr:@supabase/supabase-js@2";

const RESEND_API_KEY = Deno.env.get("RESEND_API_KEY");
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY") ?? "";
const TABLE_NAME = "information";
const COUNT_THRESHOLD = 3;
const COUNT_FIELD_NAME = "count";
const EMAIL_FIELD_NAME = "email";
const EMAIL_SUBJECT = "Welcome to TeeTrack!";
const EMAIL_FROM = "support@leettrack.dev";

if (!RESEND_API_KEY || !SUPABASE_URL || !SUPABASE_ANON_KEY) {
    throw new Error("Missing environment variables: RESEND_API_KEY, SUPABASE_URL, SUPABASE_ANON_KEY");
}

Deno.serve(async (_req: Request) => {
    const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
    
    
    // Fetch users with count < 3
    const { data: users, error } = await supabase
        .from(TABLE_NAME)
        .select(`id, ${EMAIL_FIELD_NAME}, ${COUNT_FIELD_NAME}`)
        .lt(COUNT_FIELD_NAME, COUNT_THRESHOLD);

    if (error || !users) {
        return new Response(JSON.stringify({ error: error?.message || "Failed to fetch users" }), {
            status: 500,
            headers: { "Content-Type": "application/json" },
        });
    }

    const responses = [];

    for (const user of users) {
        const cleanEmail = user.email.trim();

        // Skip invalid email formats (very basic check)
        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(cleanEmail)) {
            responses.push({ email: cleanEmail, result: "Invalid email format. Skipped." });
            continue;
        }

        const htmlContent = EMAIL_TEMPLATE.replace("{{email}}", cleanEmail);

        const emailRes = await fetch("https://api.resend.com/emails", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                Authorization: `Bearer ${RESEND_API_KEY}`,
            },
            body: JSON.stringify({
                from: EMAIL_FROM,
                to: cleanEmail,
                subject: EMAIL_SUBJECT,
                html: htmlContent,
            }),
        });

        const emailData = await emailRes.json();
        responses.push({ email: cleanEmail, result: emailData });

        // Increment count
        await supabase
            .from(TABLE_NAME)
            .update({ [COUNT_FIELD_NAME]: user[COUNT_FIELD_NAME] + 1 })
            .eq("id", user.id);
    }

    responses.forEach(response => {
        console.log(`Email sent to: ${response.email}, Result: ${JSON.stringify(response.result)}`);
    });

    console.log(`Total emails sent: ${responses.length}`);

    return new Response(JSON.stringify({ status: "completed", responses }), {
        headers: { "Content-Type": "application/json" },
    });
});

Setting Up the Cron Job in Supabase

After implementing the edge function, you need to set up a cron job to execute it automatically on your desired schedule.

1. Enable Required Extensions

First, go to the Supabase dashboard and enable the required extensions:

Enabling cron and vault extensions in Supabase

Navigate to Integrations and enable pg_cron and pg_vault extensions

2. Access Cron Jobs Interface

After enabling the extensions, you'll have access to the cron jobs feature:

Accessing the cron jobs interface in Supabase

The cron jobs interface where you'll set up your scheduled tasks

3. Configure Your Cron Job

Set up the cron schedule based on your requirements and specify the invocation method:

Configuring cron schedule and invocation method

Configure your cron schedule and set POST as the invocation method

4. Set Up Authorization

Important: Make sure to set the HTTP headers with proper authorization:

Setting up authorization headers for the cron job

Select "Add auth header with service key" to ensure proper authorization

Conclusion

By leveraging Supabase Edge Functions and cron jobs, you can implement a scalable and maintainable system for automated email follow-ups with minimal infrastructure overhead. This approach not only simplifies scheduled communications but also integrates seamlessly with your existing Supabase database and external email providers like Resend.

The outlined solution ensures retry logic, prevents duplicate sends, and remains flexible for future enhancements such as dynamic content, segmentation, or conditional logic. With this setup in place, you’re well-positioned to deliver timely and consistent user engagement at scale.

Comments

Join the discussion on “Setting Up Automated Email Follow-ups with Supabase and Cron Jobs”

3 Comments

J

John Doe

Nov 15, 2023

Great article! I learned a lot from this.

J
Jane Smith
Nov 15, 2023

I agree! The technical details were very clear.

A

Anonymous

Nov 16, 2023

I have a question about the third point you made. Could you elaborate more on that?

J

Jane Smith

Nov 17, 2023

This is exactly what I was looking for. Thanks for sharing your insights!

A
Anonymous
Nov 17, 2023

Could you share what specifically you found helpful?

J
Jane Smith
Nov 17, 2023

The implementation details in the middle section were exactly what I needed for my project.