notify_tenant
Send WhatsApp notifications to tenants about maintenance ticket updates using predefined templates or custom messages, with SMS fallback if needed.
Instructions
Sends a WhatsApp message to the tenant associated with a ticket. Supports five message templates: acknowledgement, scheduled, update, resolved, delay. Pass customMessage to override the template. Falls back to SMS if WhatsApp fails. Logs the notification to n8n and appends to ticket internal notes.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ticketId | Yes | The ID of the ticket whose tenant should be notified. | |
| messageType | Yes | Template to use: acknowledgement — ticket received, being reviewed scheduled — repair booked, include scheduledFor date update — general progress note resolved — work complete delay — work delayed, reason explained | |
| customMessage | No | Optional custom message body. When provided, overrides the built-in template. Useful for specific instructions or unusual situations. | |
| scheduledFor | No | ISO 8601 datetime — required when messageType is 'scheduled'. The template will format this into a human-readable date/time for the tenant. |
Implementation Reference
- The main handler function that executes the notify_tenant tool logic - retrieves ticket from store, validates input, builds message using template, sends via WhatsApp/SMS, logs to n8n webhook, and appends to ticket notes
export async function handleNotifyTenant( input: NotifyTenantInput ): Promise<NotifyTenantOutput> { const ticket = ticketStore.getById(input.ticketId); if (!ticket) { throw new Error(`Ticket not found: ${input.ticketId}`); } if (input.messageType === "scheduled" && !input.scheduledFor && !ticket.scheduledFor) { throw new Error( 'messageType "scheduled" requires a scheduledFor datetime. ' + "Provide it in the tool input or update the ticket status first with a scheduledFor value." ); } const scheduledFor = input.scheduledFor ?? ticket.scheduledFor; const body = buildTenantMessage( input.messageType, ticket.tenant.name, ticket.title, input.customMessage, scheduledFor ); const result = await sendWhatsApp(ticket.tenant.phone, body); // Log to n8n asynchronously triggerMaintenanceWebhook({ eventType: "tenant_notified", ticketId: ticket.id, ticketTitle: ticket.title, tenantName: ticket.tenant.name, tenantPhone: ticket.tenant.phone, messageType: input.messageType, messageBody: body, channel: result.channel, messageSid: result.messageSid, timestamp: result.timestamp, }).catch((err) => console.error("[notify_tenant] n8n webhook error:", err) ); // Append to internal notes ticketStore.appendNote( input.ticketId, `[${result.timestamp}] Tenant notified (${input.messageType}) via ${result.channel}. ` + `SID: ${result.messageSid ?? "N/A"}` ); return result; } - Input schema definition using Zod - defines ticketId, messageType enum (acknowledgement/scheduled/update/resolved/delay), optional customMessage, and optional scheduledFor datetime
export const NotifyTenantSchema = z.object({ ticketId: z.string().describe("The ID of the ticket whose tenant should be notified."), messageType: z .enum(["acknowledgement", "scheduled", "update", "resolved", "delay"]) .describe( "Template to use:\n" + " acknowledgement — ticket received, being reviewed\n" + " scheduled — repair booked, include scheduledFor date\n" + " update — general progress note\n" + " resolved — work complete\n" + " delay — work delayed, reason explained" ), customMessage: z .string() .optional() .describe( "Optional custom message body. When provided, overrides the built-in template. " + "Useful for specific instructions or unusual situations." ), scheduledFor: z .string() .optional() .describe( "ISO 8601 datetime — required when messageType is 'scheduled'. " + "The template will format this into a human-readable date/time for the tenant." ), }); export type NotifyTenantInput = z.infer<typeof NotifyTenantSchema>; - src/mcp-server/index.ts:97-116 (registration)MCP server tool registration - registers 'notify_tenant' tool with description, schema, and async handler that calls handleNotifyTenant and returns JSON result
server.tool( "notify_tenant", "Sends a WhatsApp message to the tenant associated with a ticket. " + "Supports five message templates: acknowledgement, scheduled, update, resolved, delay. " + "Pass customMessage to override the template. Falls back to SMS if WhatsApp fails. " + "Logs the notification to n8n and appends to ticket internal notes.", NotifyTenantSchema.shape, async (input) => { const result = await handleNotifyTenant(input); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } ); - src/integrations/whatsapp.ts:21-70 (helper)Message template builder function - creates formatted WhatsApp messages based on message type (acknowledgement, scheduled, update, resolved, delay) with tenant name, ticket title, and scheduled time
export function buildTenantMessage( messageType: string, tenantName: string, ticketTitle: string, customMessage?: string, scheduledFor?: string ): string { const firstName = tenantName.split(" ")[0]; if (customMessage) return customMessage; const formattedTime = scheduledFor ? new Date(scheduledFor).toLocaleString("en-US", { weekday: "long", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", hour12: true, }) : "soon"; const templates: Record<string, string> = { acknowledgement: `Hi ${firstName}! 👋 We've received your maintenance request: *"${ticketTitle}"*. ` + `Our team is reviewing it now and will be in touch shortly with next steps. ` + `Reply STOP to unsubscribe from updates.`, scheduled: `Hi ${firstName}! Great news — your maintenance request *"${ticketTitle}"* has been scheduled. ` + `A technician will visit on *${formattedTime}*. ` + `Please ensure someone is home to provide access. Reply if you need to reschedule.`, update: `Hi ${firstName}! Quick update on your request *"${ticketTitle}"*: ` + `our team is actively working on it. We'll notify you once it's resolved.`, resolved: `Hi ${firstName}! ✅ Your maintenance request *"${ticketTitle}"* has been resolved. ` + `Please let us know if the issue persists or if you have any concerns. ` + `Thank you for your patience!`, delay: `Hi ${firstName}, we want to keep you informed — there's been a short delay with ` + `your request *"${ticketTitle}"*. We're working to reschedule as soon as possible ` + `and will confirm the new time shortly. Apologies for the inconvenience.`, }; return templates[messageType] ?? templates["update"]; } - src/types.ts:116-123 (schema)Output type definition - defines NotifyTenantOutput interface with success boolean, channel type (whatsapp/sms/simulated), messageSid, to, body, and timestamp fields
export interface NotifyTenantOutput { success: boolean; channel: "whatsapp" | "sms" | "simulated"; messageSid?: string; to: string; body: string; timestamp: string; }