Sending Messages via RPC
Authenticated - Send messages to users from HTTP endpoints or other Workers
Since Actors are Durable Objects, you can call their methods remotely using RPC. This is perfect for sending notifications from REST APIs, webhooks, or scheduled tasks.
Basic RPC Example: Send Notification from HTTP Endpoint
Room Definition:
import { defineRoom } from "verani";
export const notificationsRoom = defineRoom({
name: "notifications",
websocketPath: "/notifications",
onConnect(ctx) {
console.log(`User ${ctx.meta.userId} connected to notifications`);
}
});
// Register event handlers if needed (socket.io-like)
// notificationsRoom.on("some.event", (ctx, data) => {
// // Handle client messages
// });
Worker with RPC Endpoint:
import { createActorHandler } from "verani";
import { notificationsRoom } from "./actors/notifications.actor"; // Suggested: src/actors/ folder (optional)
const NotificationsRoom = createActorHandler(notificationsRoom);
export { NotificationsRoom };
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// WebSocket connections
if (url.pathname.startsWith("/notifications")) {
const stub = NotificationsRoom.get("notifications");
return stub.fetch(request);
}
// HTTP endpoint to send notifications via RPC
if (url.pathname === "/api/send-notification" && request.method === "POST") {
// Verify authentication (simplified - use proper auth in production)
const authHeader = request.headers.get("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
return new Response("Unauthorized", { status: 401 });
}
const { userId, message, type = "info" } = await request.json();
// Get Actor stub (variable name must match wrangler.jsonc class_name)
const stub = NotificationsRoom.get(`notifications:${userId}`);
// Send notification via RPC (Socket.IO-like API - direct method call)
const sentCount = await stub.emitToUser(userId, "notification", {
notificationType: type,
message,
timestamp: Date.now()
});
return Response.json({
success: true,
sentTo: sentCount,
message: `Notification sent to ${sentCount} session(s)`
});
}
return new Response("Not Found", { status: 404 });
}
};
Usage:
# Send notification via HTTP
curl -X POST https://your-worker.dev/api/send-notification \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "alice",
"message": "You have a new message",
"type": "info"
}'
Query Actor State via RPC
Get real-time statistics about connected users:
// In your Worker fetch handler
if (url.pathname === "/api/stats") {
const stub = NotificationsRoom.get("notifications");
// Query actor state via RPC
const [count, userIds] = await Promise.all([
stub.getSessionCount(),
stub.getConnectedUserIds()
]);
return Response.json({
onlineUsers: count,
userIds,
timestamp: Date.now()
});
}
Broadcast from External Event
Send announcements to all users in a channel:
import { createActorHandler } from "verani";
import { chatRoom } from "./actors/chat.actor"; // Suggested: src/actors/ folder (optional)
const ChatRoom = createActorHandler(chatRoom);
export { ChatRoom };
// Webhook handler for external events
if (url.pathname === "/webhook/announcement" && request.method === "POST") {
const { announcement, channel = "default", targetUsers } = await request.json();
const stub = ChatRoom.get("chat-room");
// Broadcast via RPC (Socket.IO-like API - direct method call)
// Note: For user filtering, use legacy broadcast() API
let sentCount: number;
if (targetUsers) {
// Legacy API needed for filtering
const opts = { userIds: targetUsers };
sentCount = await stub.broadcast(channel, {
type: "announcement",
text: announcement,
timestamp: Date.now()
}, opts);
} else {
// Socket.IO-like API - direct method call
sentCount = await stub.emitToChannel(channel, "announcement", {
text: announcement,
timestamp: Date.now()
});
}
return Response.json({
success: true,
sentTo: sentCount,
message: `Announcement sent to ${sentCount} connection(s)`
});
}
Scheduled Notifications
Send notifications from scheduled tasks (Cron Triggers):
// In wrangler.jsonc, add:
// "triggers": { "crons": ["0 9 * * *"] }
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// ... existing fetch handler
},
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
// Get list of users who should receive daily digest
const usersToNotify = await getUsersForDailyDigest();
// Send to each user's notification Actor
for (const userId of usersToNotify) {
const stub = NotificationsRoom.get(`notifications:${userId}`);
// Socket.IO-like API - direct method call
await stub.emitToUser(userId, "daily-digest", {
date: new Date().toISOString(),
summary: await getDailySummary(userId)
});
}
}
};
RPC from Another Actor
Call Actor methods from other Actors:
import { createActorHandler } from "verani";
import { otherRoom } from "./actors/other.actor"; // Suggested: src/actors/ folder (optional)
const OtherRoom = createActorHandler(otherRoom);
export { OtherRoom };
// In one Actor's event handler (socket.io-like)
room.on("cross-room-message", async (ctx, data) => {
const { targetRoom, targetUser, message } = data;
// Get another Actor's stub
const targetStub = OtherRoom.get(targetRoom);
// Send message via RPC (Socket.IO-like API - direct method call)
await targetStub.emitToUser(targetUser, "cross-room", {
from: ctx.meta.userId,
message
});
});
Key Points
- Socket.IO-like API: Use
stub.emitToChannel()andstub.emitToUser()for a unified, familiar API with direct method calls - Always use
await: RPC methods return Promises even if the underlying method is synchronous - Direct method calls: Simple, type-safe API without complex builder patterns
- Legacy API still available:
sendToUser()andbroadcast()are deprecated but still work for backward compatibility - Use legacy API for filtering: If you need
userIdsorclientIdsfiltering, use the legacybroadcast()method - Actor ID consistency: Use the same ID string for WebSocket connections and RPC calls to reach the same Actor instance
- Variable name must match wrangler.jsonc: The exported variable name must match the
class_namein wrangler.jsonc - Error handling: RPC calls can fail - wrap in try/catch
- Performance: RPC calls have network overhead - batch operations when possible
Error Handling
try {
// Socket.IO-like API - direct method call
const sentCount = await stub.emitToUser(userId, "message", data);
console.log(`Sent to ${sentCount} sessions`);
} catch (error) {
console.error("RPC call failed:", error);
// Handle error (retry, log, etc.)
}
Related Examples
- Authentication - Secure authentication
- Basic Chat - Simple chat room
Related Documentation
- RPC Guide - Complete RPC guide
- Server API - RPC Methods - RPC API reference