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:

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:

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:

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:

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.