<html lang="en">
<head></head>
<body>

<form id="mainForm" method="post" action="https://stackblitz.com/run" target="_self">
<input type="hidden" name="project[files][.gitignore]" value=".actorcore
node_modules
">
<input type="hidden" name="project[files][README.md]" value="# Daily Email Campaign for RivetKit

Example project demonstrating scheduled background emails with [RivetKit](https://rivetkit.org).

[Learn More →](https://github.com/rivet-dev/rivetkit)

[Discord](https://rivet.dev/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-dev/rivetkit/issues)

## Getting Started

### Prerequisites

- Node.js 20+
- [Resend](https://resend.com) API key and verified sender domain

### Installation

```sh
git clone https://github.com/rivet-dev/rivetkit
cd rivetkit/examples/background-jobs
npm install
```

### Development

```sh
RESEND_API_KEY=your-api-key \
RESEND_FROM_EMAIL=&quot;Example &lt;hello@example.com&gt;&quot; \
CAMPAIGN_USER_EMAIL=user@example.com \
npm run dev
```

The example creates a single `emailCampaignUser` actor, stores the recipient email, and schedules a daily task that sends mail through the live Resend API. The server logs the next scheduled send time, and the actor reschedules itself after each successful delivery. Set `CAMPAIGN_USER_ID` to control the actor key when you need to track multiple users.

## License

Apache 2.0
">
<input type="hidden" name="project[files][package.json]" value="{&quot;name&quot;:&quot;example-background-jobs&quot;,&quot;version&quot;:&quot;2.0.20&quot;,&quot;private&quot;:true,&quot;type&quot;:&quot;module&quot;,&quot;scripts&quot;:{&quot;dev&quot;:&quot;tsx src/server.ts&quot;,&quot;check-types&quot;:&quot;tsc --noEmit&quot;,&quot;test&quot;:&quot;vitest run&quot;},&quot;devDependencies&quot;:{&quot;rivetkit&quot;:&quot;https://pkg.pr.new/rivet-dev/rivetkit/rivetkit@54999214372f9ecb3f4251a3be45c7a0eb8dfacf&quot;,&quot;@types/node&quot;:&quot;^22.13.9&quot;,&quot;tsx&quot;:&quot;^3.12.7&quot;,&quot;typescript&quot;:&quot;^5.7.3&quot;,&quot;vitest&quot;:&quot;^3.1.1&quot;},&quot;dependencies&quot;:{&quot;resend&quot;:&quot;^4.0.1&quot;},&quot;stableVersion&quot;:&quot;0.8.0&quot;}">
<input type="hidden" name="project[files][tsconfig.json]" value="{
  &quot;compilerOptions&quot;: {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    &quot;target&quot;: &quot;esnext&quot;,
    /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    &quot;lib&quot;: [&quot;esnext&quot;],
    /* Specify what JSX code is generated. */
    &quot;jsx&quot;: &quot;react-jsx&quot;,

    /* Specify what module code is generated. */
    &quot;module&quot;: &quot;esnext&quot;,
    /* Specify how TypeScript looks up a file from a given module specifier. */
    &quot;moduleResolution&quot;: &quot;bundler&quot;,
    /* Specify type package names to be included without being referenced in a source file. */
    &quot;types&quot;: [&quot;node&quot;],
    /* Enable importing .json files */
    &quot;resolveJsonModule&quot;: true,

    /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
    &quot;allowJs&quot;: true,
    /* Enable error reporting in type-checked JavaScript files. */
    &quot;checkJs&quot;: false,

    /* Disable emitting files from a compilation. */
    &quot;noEmit&quot;: true,

    /* Ensure that each file can be safely transpiled without relying on other imports. */
    &quot;isolatedModules&quot;: true,
    /* Allow &#39;import x from y&#39; when a module doesn&#39;t have a default export. */
    &quot;allowSyntheticDefaultImports&quot;: true,
    /* Ensure that casing is correct in imports. */
    &quot;forceConsistentCasingInFileNames&quot;: true,

    /* Enable all strict type-checking options. */
    &quot;strict&quot;: true,

    /* Skip type checking all .d.ts files. */
    &quot;skipLibCheck&quot;: true
  },
  &quot;include&quot;: [&quot;src/**/*.ts&quot;, &quot;scripts/**/*.ts&quot;, &quot;tests/**/*.ts&quot;]
}
">
<input type="hidden" name="project[files][turbo.json]" value="{
  &quot;$schema&quot;: &quot;https://turbo.build/schema.json&quot;,
  &quot;extends&quot;: [&quot;//&quot;]
}
">
<input type="hidden" name="project[files][src/registry.ts]" value="import { Resend } from &quot;resend&quot;;
import { actor, setup } from &quot;rivetkit&quot;;
import type { CampaignInput, CampaignState } from &quot;./types&quot;;

const DAY_IN_MS = 86_400_000;

const EMAIL_SUBJECT = &quot;Daily campaign update&quot;;
const EMAIL_BODY = [
	&quot;&lt;p&gt;Hi there,&lt;/p&gt;&quot;,
	&quot;&lt;p&gt;This is your automated daily campaign email from RivetKit.&lt;/p&gt;&quot;,
	&quot;&lt;p&gt;Have a great day!&lt;/p&gt;&quot;,
].join(&quot;&quot;);

const emailCampaignUser = actor({
	createState: (_c, input: CampaignInput): CampaignState =&gt; ({
		email: input.email,
	}),

	onCreate: async (c) =&gt; {
		const nextSendAt = Date.now() + DAY_IN_MS;
		c.state.nextSendAt = nextSendAt;
		await c.schedule.at(nextSendAt, &quot;sendDailyEmail&quot;);
	},

	actions: {
		sendDailyEmail: async (c) =&gt; {
			const resend = new Resend(process.env.RESEND_API_KEY ?? &quot;&quot;);

			const { data, error } = await resend.emails.send({
				from: process.env.RESEND_FROM_EMAIL ?? &quot;&quot;,
				to: c.state.email,
				subject: EMAIL_SUBJECT,
				html: EMAIL_BODY,
			});

			c.state.lastSentAt = Date.now();
			c.state.lastMessageId = data?.id ?? String(error ?? &quot;&quot;);

			const nextSendAt = Date.now() + DAY_IN_MS;
			c.state.nextSendAt = nextSendAt;
			await c.schedule.at(nextSendAt, &quot;sendDailyEmail&quot;);
		},

		getStatus: (c) =&gt; c.state,
	},
});

export const registry = setup({
	use: { emailCampaignUser },
});

export type Registry = typeof registry;
">
<input type="hidden" name="project[files][src/server.ts]" value="import { registry } from &quot;./registry&quot;;

const { client } = registry.start();

async function main() {
	const userEmail = process.env.CAMPAIGN_USER_EMAIL;
	const userId = process.env.CAMPAIGN_USER_ID ?? &quot;demo-user&quot;;

	if (!userEmail) {
		console.warn(
			&quot;Set CAMPAIGN_USER_EMAIL to schedule the daily email campaign (e.g. CAMPAIGN_USER_EMAIL=user@example.com).&quot;,
		);
		return;
	}

	const campaign = client.emailCampaignUser.getOrCreate(userId, {
		createWithInput: { email: userEmail },
	});
	const status = await campaign.getStatus();

	const nextSend = status.nextSendAt
		? new Date(status.nextSendAt).toISOString()
		: &quot;not scheduled&quot;;

	console.log(`Next daily email for ${status.email} scheduled at ${nextSend}`);
}

void main();
">
<input type="hidden" name="project[files][src/types.ts]" value="export type CampaignInput = {
	email: string;
};

export type CampaignState = CampaignInput &amp; {
	lastSentAt?: number;
	lastMessageId?: string;
	nextSendAt?: number;
};
">
<input type="hidden" name="project[description]" value="generated by https://pkg.pr.new">
<input type="hidden" name="project[template]" value="node">
<input type="hidden" name="project[title]" value="example-background-jobs">
</form>
<script>document.getElementById("mainForm").submit();</script>

</body></html>