create-scheduled-message
Schedule messages in Zulip for future delivery. Specify recipients, content, and a timestamp to automatically send messages to streams or direct users at the chosen time.
Instructions
Schedule a message to be sent at a future time. For direct messages, use comma-separated email addresses or get user info from the users-directory resource (zulip://users).
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| content | Yes | Message content with Markdown formatting | |
| scheduled_delivery_timestamp | Yes | Unix timestamp when message should be sent (seconds since epoch) | |
| to | Yes | For streams: channel name (e.g., 'general'). For direct: comma-separated user emails (e.g., 'user@example.com,user2@example.com') | |
| topic | No | Topic for stream messages | |
| type | Yes | Message type: 'stream' for channels, 'direct' for private messages |
Implementation Reference
- src/server.ts:639-662 (registration)MCP tool registration including name, description, schema reference, and inline handler function that delegates to ZulipClient.server.tool( "create-scheduled-message", "Schedule a message to be sent at a future time. For direct messages, use comma-separated email addresses or get user info from the users-directory resource (zulip://users).", CreateScheduledMessageSchema.shape, async ({ type, to, content, topic, scheduled_delivery_timestamp }) => { try { const result = await zulipClient.createScheduledMessage({ type, to, content, topic, scheduled_delivery_timestamp }); return createSuccessResponse(JSON.stringify({ success: true, scheduled_message_id: result.scheduled_message_id, delivery_time: new Date(scheduled_delivery_timestamp * 1000).toISOString(), message: `Message scheduled successfully! ID: ${result.scheduled_message_id}` }, null, 2)); } catch (error) { return createErrorResponse(`Error creating scheduled message: ${error instanceof Error ? error.message : 'Unknown error'}`); } } );
- src/server.ts:643-661 (handler)Handler function that executes the tool: calls ZulipClient.createScheduledMessage and formats MCP success/error response.async ({ type, to, content, topic, scheduled_delivery_timestamp }) => { try { const result = await zulipClient.createScheduledMessage({ type, to, content, topic, scheduled_delivery_timestamp }); return createSuccessResponse(JSON.stringify({ success: true, scheduled_message_id: result.scheduled_message_id, delivery_time: new Date(scheduled_delivery_timestamp * 1000).toISOString(), message: `Message scheduled successfully! ID: ${result.scheduled_message_id}` }, null, 2)); } catch (error) { return createErrorResponse(`Error creating scheduled message: ${error instanceof Error ? error.message : 'Unknown error'}`); } }
- src/types.ts:147-153 (schema)Zod schema defining input parameters and descriptions for the create-scheduled-message tool.export const CreateScheduledMessageSchema = z.object({ type: z.enum(["stream", "direct"]).describe("Message type: 'stream' for channels, 'direct' for private messages"), to: z.string().describe("For streams: channel name (e.g., 'general'). For direct: comma-separated user emails (e.g., 'user@example.com,user2@example.com')"), content: z.string().describe("Message content with Markdown formatting"), topic: z.string().optional().describe("Topic for stream messages"), scheduled_delivery_timestamp: z.number().describe("Unix timestamp when message should be sent (seconds since epoch)") });
- src/zulip/client.ts:250-294 (helper)ZulipClient method implementing the core logic: payload preparation for stream/direct messages and API POST to /scheduled_messages endpoint.async createScheduledMessage(params: { type: 'stream' | 'direct'; to: string; content: string; topic?: string; scheduled_delivery_timestamp: number; }): Promise<{ scheduled_message_id: number }> { // Convert our types to Zulip API types const zulipType = params.type === 'direct' ? 'private' : 'stream'; const payload: any = { type: zulipType, content: params.content, scheduled_delivery_timestamp: params.scheduled_delivery_timestamp }; // Handle recipients based on message type if (params.type === 'direct') { // For private messages, 'to' should be JSON array of user emails/IDs const recipients = params.to.split(',').map(email => email.trim()); payload.to = JSON.stringify(recipients); } else { // For stream messages, 'to' is the stream name payload.to = params.to; if (params.topic) { payload.topic = params.topic; } } const response = await this.client.post('/scheduled_messages', payload, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, transformRequest: [(data) => { const params = new URLSearchParams(); for (const key in data) { if (data[key] !== undefined) { params.append(key, String(data[key])); } } return params.toString(); // Return string, not URLSearchParams object }] }); return response.data; }