Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.

Commit fc6d0c7

Browse files
authored
chore(examples): add bots, background-jobs, and workflows examples (#1395)
1 parent d1391f6 commit fc6d0c7

39 files changed

+887
-112
lines changed

examples/ai-agent/src/backend/registry.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,8 @@ import { openai } from "@ai-sdk/openai";
22
import { generateText, tool } from "ai";
33
import { actor, setup } from "rivetkit";
44
import { z } from "zod";
5-
import { getWeather } from "./my-utils";
6-
7-
export type Message = {
8-
role: "user" | "assistant";
9-
content: string;
10-
timestamp: number;
11-
};
5+
import { getWeather } from "./my-tools";
6+
import type { Message } from "./types";
127

138
export const aiAgent = actor({
149
// Persistent state that survives restarts: https://rivet.dev/docs/actors/state
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type Message = {
2+
role: "user" | "assistant";
3+
content: string;
4+
timestamp: number;
5+
};

examples/ai-agent/src/frontend/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createRivetKit } from "@rivetkit/react";
22
import { useEffect, useState } from "react";
3-
import type { Message, registry } from "../backend/registry";
3+
import { registry } from "../backend/registry";
4+
import type { Message } from "../backend/types";
45

56
const { useActor } = createRivetKit<typeof registry>("http://localhost:8080");
67

examples/ai-agent/tests/ai-agent.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ vi.mock("ai", () => ({
1414
tool: vi.fn().mockImplementation(({ execute }) => ({ execute })),
1515
}));
1616

17-
vi.mock("../src/backend/my-utils", () => ({
17+
vi.mock("../src/backend/my-tools", () => ({
1818
getWeather: vi.fn().mockResolvedValue({
1919
location: "San Francisco",
2020
temperature: 72,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.actorcore
2+
node_modules

examples/background-jobs/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Daily Email Campaign for RivetKit
2+
3+
Example project demonstrating scheduled background emails with [RivetKit](https://rivetkit.org).
4+
5+
[Learn More →](https://github.com/rivet-dev/rivetkit)
6+
7+
[Discord](https://rivet.dev/discord)[Documentation](https://rivetkit.org)[Issues](https://github.com/rivet-dev/rivetkit/issues)
8+
9+
## Getting Started
10+
11+
### Prerequisites
12+
13+
- Node.js 20+
14+
- [Resend](https://resend.com) API key and verified sender domain
15+
16+
### Installation
17+
18+
```sh
19+
git clone https://github.com/rivet-dev/rivetkit
20+
cd rivetkit/examples/background-jobs
21+
npm install
22+
```
23+
24+
### Development
25+
26+
```sh
27+
RESEND_API_KEY=your-api-key \
28+
RESEND_FROM_EMAIL="Example <[email protected]>" \
29+
30+
npm run dev
31+
```
32+
33+
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.
34+
35+
## License
36+
37+
Apache 2.0
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "example-background-jobs",
3+
"version": "2.0.14",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "tsx src/server.ts",
8+
"check-types": "tsc --noEmit",
9+
"test": "vitest run"
10+
},
11+
"devDependencies": {
12+
"rivetkit": "workspace:*",
13+
"@types/node": "^22.13.9",
14+
"tsx": "^3.12.7",
15+
"typescript": "^5.7.3",
16+
"vitest": "^3.1.1"
17+
},
18+
"dependencies": {
19+
"resend": "^4.0.1"
20+
},
21+
"stableVersion": "0.8.0"
22+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Resend } from "resend";
2+
import { actor, setup } from "rivetkit";
3+
import type { CampaignInput, CampaignState } from "./types";
4+
5+
const DAY_IN_MS = 86_400_000;
6+
7+
const EMAIL_SUBJECT = "Daily campaign update";
8+
const EMAIL_BODY = [
9+
"<p>Hi there,</p>",
10+
"<p>This is your automated daily campaign email from RivetKit.</p>",
11+
"<p>Have a great day!</p>",
12+
].join("");
13+
14+
const emailCampaignUser = actor({
15+
createState: (_c, input: CampaignInput): CampaignState => ({
16+
email: input.email,
17+
}),
18+
19+
onCreate: async (c) => {
20+
const nextSendAt = Date.now() + DAY_IN_MS;
21+
c.state.nextSendAt = nextSendAt;
22+
await c.schedule.at(nextSendAt, "sendDailyEmail");
23+
},
24+
25+
actions: {
26+
sendDailyEmail: async (c) => {
27+
const resend = new Resend(process.env.RESEND_API_KEY ?? "");
28+
29+
const { data, error } = await resend.emails.send({
30+
from: process.env.RESEND_FROM_EMAIL ?? "",
31+
to: c.state.email,
32+
subject: EMAIL_SUBJECT,
33+
html: EMAIL_BODY,
34+
});
35+
36+
c.state.lastSentAt = Date.now();
37+
c.state.lastMessageId = data?.id ?? String(error ?? "");
38+
39+
const nextSendAt = Date.now() + DAY_IN_MS;
40+
c.state.nextSendAt = nextSendAt;
41+
await c.schedule.at(nextSendAt, "sendDailyEmail");
42+
},
43+
44+
getStatus: (c) => c.state,
45+
},
46+
});
47+
48+
export const registry = setup({
49+
use: { emailCampaignUser },
50+
});
51+
52+
export type Registry = typeof registry;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { registry } from "./registry";
2+
3+
const { client } = registry.start();
4+
5+
async function main() {
6+
const userEmail = process.env.CAMPAIGN_USER_EMAIL;
7+
const userId = process.env.CAMPAIGN_USER_ID ?? "demo-user";
8+
9+
if (!userEmail) {
10+
console.warn(
11+
"Set CAMPAIGN_USER_EMAIL to schedule the daily email campaign (e.g. [email protected]).",
12+
);
13+
return;
14+
}
15+
16+
const campaign = client.emailCampaignUser.getOrCreate(userId, {
17+
createWithInput: { email: userEmail },
18+
});
19+
const status = await campaign.getStatus();
20+
21+
const nextSend = status.nextSendAt
22+
? new Date(status.nextSendAt).toISOString()
23+
: "not scheduled";
24+
25+
console.log(`Next daily email for ${status.email} scheduled at ${nextSend}`);
26+
}
27+
28+
void main();

0 commit comments

Comments
 (0)