Resend is an email API service that works seamlessly with ActorCore for handling emails and notifications.

Example

See how ActorCore and Resend can power engagement with daily streak notifications.

Streaks

View on GitHub

Quickstart

1

Install Resend SDK

npm install resend
2

Create an actor with Resend

actors.ts
import { actor, setup } from "actor-core";
import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

const user = actor({
  state: {
    email: null as string | null,
  },

  actions: {
    // Example: Somehow acquire the user's email
    register: async (c, email: string) => {
      c.state.email = email;
    },

    // Example: Send an email
    sendExampleEmail: async (c) => {
      if (!c.state.email) throw new Error("No email registered");
      
      await resend.emails.send({
        from: "[email protected]",
        to: c.state.email,
        subject: "Hello, world!",
        html: "<p>Lorem ipsum</p>",
      });
    },
  },
});

export const app = setup({ actors: { user } });
export type App = typeof app;
3

Call your actor

client.ts
import { createClient } from "actor-core";
import { App } from "./actors/app.ts";

const client = createClient<App>("http://localhost:8787");
const userActor = await client.user.get({ tags: { user: "user123" } });

await userActor.register("[email protected]");
await userActor.sendExampleEmail();

Use Cases

Scheduling Emails

ActorCore’s scheduling capabilities with Resend make it easy to send emails at specific times:

const emailScheduler = actor({
  state: {
    email: null as string | null,
  },

  actions: {
    scheduleEmail: async (c, email: string, delayMs: number = 86400000) => {
      c.state.email = email;
      await c.schedule.at(Date.now() + delayMs, "sendEmail");
    },
    
    sendEmail: async (c) => {
      if (!c.state.email) return;
      
      await resend.emails.send({
        from: "[email protected]",
        to: c.state.email,
        subject: "Your scheduled message",
        html: "<p>This email was scheduled earlier!</p>",
      });
    },
  },
});

Daily Reminders

Send daily reminders to users based on their activity:

const reminder = actor({
  state: {
    email: null as string | null,
    lastActive: null as number | null,
  },

  actions: {
    trackActivity: async (c, email: string) => {
      c.state.email = email;
      c.state.lastActive = Date.now();
      
      // Schedule check for tomorrow
      await c.schedule.at(Date.now() + 24 * 60 * 60 * 1000, "checkActivity");
    },
    
    checkActivity: async (c) => {
      if (!c.state.email) return;
      
      // If inactive for 24+ hours, send reminder
      if (Date.now() - (c.state.lastActive || 0) >= 24 * 60 * 60 * 1000) {
        await resend.emails.send({
          from: "[email protected]",
          to: c.state.email,
          subject: "We miss you!",
          html: "<p>Don't forget to check in today.</p>",
        });
      }
      
      // Reschedule for tomorrow
      await c.schedule.at(Date.now() + 24 * 60 * 60 * 1000, "checkActivity");
    },
  },
});

Alerting Systems

Monitor your systems and send alerts when issues are detected:

const monitor = actor({
  state: {
    alertEmail: null as string | null,
    isHealthy: true,
  },

  actions: {
    configure: async (c, email: string) => {
      c.state.alertEmail = email;
      await c.schedule.at(Date.now() + 60000, "checkHealth");
    },
    
    checkHealth: async (c) => {
      // Simple mock health check
      const wasHealthy = c.state.isHealthy;
      c.state.isHealthy = await mockHealthCheck();
      
      // Alert on status change to unhealthy
      if (wasHealthy && !c.state.isHealthy && c.state.alertEmail) {
        await resend.emails.send({
          from: "[email protected]",
          to: c.state.alertEmail,
          subject: "⚠️ System Alert",
          html: "<p>The system is experiencing issues.</p>",
        });
      }
      
      // Reschedule next check
      await c.schedule.at(Date.now() + 60000, "checkHealth");
    },
  },
});

// Mock function
async function mockHealthCheck() {
  return Math.random() > 0.1; // 90% chance of being healthy
}

Testing

When testing actors that use Resend, you should mock the Resend API to avoid sending real emails during tests. ActorCore’s testing utilities combined with Vitest make this straightforward:

import { test, expect, vi, beforeEach } from "vitest";
import { setupTest } from "actor-core/test";
import { app } from "../actors/app";

// Create mock for send method
const mockSendEmail = vi.fn().mockResolvedValue({ success: true });

beforeEach(() => {
  process.env.RESEND_API_KEY = "test_mock_api_key_12345";

  vi.mock("resend", () => {
    return {
      Resend: vi.fn().mockImplementation(() => {
        return {
          emails: {
            send: mockSendEmail
          }
        };
      })
    };
  });

  mockSendEmail.mockClear();
});

test("email is sent when action is called", async (t) => {
  const { client } = await setupTest(t, app);
  const actor = await client.user.get();

  // Call the action that should send an email
  await actor.someActionThatSendsEmail("[email protected]");

  // Verify the email was sent with the right parameters
  expect(mockSendEmail).toHaveBeenCalledWith(
    expect.objectContaining({
      to: "[email protected]",
      subject: "Expected Subject",
    }),
  );
});

Using vi.advanceTimersByTimeAsync() is particularly useful for testing scheduled emails:

// Fast forward time to test scheduled emails
await vi.advanceTimersByTimeAsync(24 * 60 * 60 * 1000); // Advance 24 hours

// Test that the scheduled email was sent
expect(mockSendEmail).toHaveBeenCalledTimes(2);