DocumentationGuidesEmail Workflows

Email Workflows

Common patterns for sending emails as background jobs with Pidgey.

1. Welcome Email

Send a welcome email when a user signs up.

jobs/send-welcome-email.ts
import { pidgey } from '@/lib/pidgey';
 
export const sendWelcomeEmail = pidgey.defineJob({
  name: 'send-welcome-email',
  handler: async (data: { userId: string; email: string }) => {
    const user = await db.users.findById(data.userId);
 
    await resend.emails.send({
      to: data.email,
      subject: `Welcome, ${user.name}!`,
      template: 'welcome',
    });
 
    return { sent: true };
  },
  config: {
    retries: 3,
    timeout: 30000,
  },
});

Enqueue the job after user signup:

app/actions/signup.ts
'use server';
 
import { sendWelcomeEmail } from '@/jobs/send-welcome-email';
 
export async function signup(formData: FormData) {
  const email = formData.get('email') as string;
  const user = await db.users.create({ email });
 
  await sendWelcomeEmail.enqueue({
    userId: user.id,
    email: user.email,
  });
 
  return { success: true };
}

2. Delayed Reminder

Send a follow-up email after a delay (e.g., 24 hours):

await sendFollowUpEmail.enqueue(
  { userId: user.id },
  { delay: 24 * 60 * 60 * 1000 } // 24 hours
);

3. Daily Digest

Scheduled jobs are ideal for recurring emails.

jobs/daily-digest.ts
export const dailyDigest = pidgey.defineScheduledJob({
  name: 'daily-digest',
  handler: async () => {
    const users = await db.users.findMany({ digestEnabled: true });
 
    for (const user of users) {
      const activity = await getActivitySummary(user.id);
      await sendDigestEmail(user.email, activity);
    }
  },
  schedule: {
    cron: '0 9 * * *', // Every day at 9am
    timezone: 'America/New_York',
  },
});
Scheduled jobs are automatically registered on worker startup.

4. Bulk Email with Rate Limiting

Use a separate queue and throttling to avoid hitting provider limits.

jobs/send-bulk-email.ts
export const sendBulkEmail = pidgey.defineJob({
  name: 'send-bulk-email',
  handler: async (data: { emails: string[]; template: string }) => {
    for (const email of data.emails) {
      await resend.emails.send({
        to: email,
        template: data.template,
      });
 
      // Small delay between emails to prevent rate limiting
      await new Promise((r) => setTimeout(r, 100));
    }
  },
  config: {
    queue: 'bulk', // Dedicated bulk queue
    timeout: 600000, // 10-minute timeout
  },
});

Run the bulk queue with low concurrency:

npx pidgey worker start --queue bulk --concurrency 1

Use dedicated queues for bulk operations to prevent slow jobs from blocking high-priority emails.

Best Practices

  • Use separate queues for high-priority vs bulk emails.
  • Throttle external API calls to prevent rate-limiting.
  • Scheduled jobs are ideal for recurring or digest emails.
  • Retries & timeouts should match the expected job complexity.

Made with ❤️ in Chicago