WhatsApp Integration
WhatsApp overview: corporate number architecture, Evolution API, ~20 templates
The WhatsApp integration is a cornerstone of Odys's communication strategy, enabling seamless interaction between professionals and their clients. This system handles everything from appointment confirmations and reminders to direct conversational messaging, all channeled through a corporate WhatsApp number. Understanding this integration is key to grasping how Odys facilitates timely and personalized client engagement.
Overview
Odys uses the Evolution API as its WhatsApp Business API client, providing the infrastructure for sending and receiving messages. This API is configured locally via docker-compose.evolution.yml, which sets up the evolution-api service. All outbound messages are meticulously crafted using a set of 19 predefined templates, ensuring consistency and clarity. Incoming messages are processed through the /api/whatsapp/webhook route, which then routes them to the appropriate professional or client, often leveraging a context-setting mechanism to maintain conversational flow. The messages database table serves as the central repository for all these interactions, linking them directly to specific professionals and clients.
Evolution API Integration
The core of the WhatsApp integration resides in src/lib/whatsapp.ts. This file acts as the primary interface for all WhatsApp communication within the Odys application. It relies on three crucial environment variables: EVOLUTION_API_URL, EVOLUTION_API_KEY, and EVOLUTION_INSTANCE, which define the endpoint, authentication, and specific WhatsApp instance to be used. The isConfigured function provides a quick check to ensure these variables are properly set before any message attempts.
The sendWhatsApp function is the single entry point for dispatching messages. It accepts a phone number, the text content, and an optional context object. Before sending, the normalizeBrazilianPhone utility ensures the phone number is in a consistent international format, specifically handling Brazilian numbers by prefixing "55" if needed, while preserving other international formats.
A critical aspect of sendWhatsApp is its context parameter. When a transactional message (like a reminder or confirmation) is sent to a client, the professionalId and optionally appointmentId are passed in this context. Upon successful delivery, setOutboundContext is called. As described in the code's comments, this function records the professionalId and appointmentId against the client's phone number in a last_outbound:{phone} key, typically in Redis. This allows the /api/whatsapp/webhook to intelligently route any subsequent client replies within a 2-hour window back to the correct professional and appointment, maintaining conversational continuity even in a stateless API environment.
The sendWhatsApp function is designed for resilience; it wraps the API call in a try-catch block and logs any failures using logger.error, returning false instead of throwing an exception. This prevents WhatsApp communication issues from cascading and disrupting other parts of the application. To prevent abuse, the /api/whatsapp endpoint is subject to a rate limit of 10 requests per minute.
The docker-compose.evolution.yml file provides the local development setup for the Evolution API. It configures the evolution-api service using the atendai/evolution-api:v1.8.2 image. Notably, it disables global webhooks (WEBHOOK_GLOBAL_ENABLED: "false"), instead relying on per-instance webhook configuration via an API call. This allows for fine-grained control over which events trigger webhooks, with the current setup explicitly mentioning MESSAGES_UPSERT as the event of interest for the /api/whatsapp/webhook endpoint.
Message Templating Strategy
To ensure a consistent brand voice and efficient communication, Odys defines all its outbound WhatsApp messages as distinct, exported functions within src/lib/whatsapp.ts. This centralized approach prevents message strings from being scattered across various API routes or services.
The system employs a visual templating strategy for client-facing transactional messages. Functions like transactionalHeader and TRANSACTIONAL_FOOTER are used to prepend and append standardized elements, including a HEADER_RULE (a line of "━━━━━━━━━━━━━━━━━"), an emoji, a label, and the professional's name. This visual structure helps clients distinguish automated notifications from direct conversational replies, which is particularly important when the same Odys WhatsApp number handles both types of interactions.
Odys currently utilizes 19 distinct message templates, covering a wide range of scenarios:
- Booking Management:
msgBookingRequest(to professional),msgBookingConfirmed,msgBookingRejected,msgBookingCancelled(to client). - Reminders:
msgReminder24h,msgReminder1h. - Cancellation Notifications:
msgCancelledByClientToPro,msgCancelledByProToClient. - Professional Receipts:
msgBookingConfirmedReceipt,msgBookingRejectedReceipt,msgPaymentConfirmedToPro,msgAppointmentCompletedToPro,msgNoShowToPro. These provide immediate feedback to professionals on actions they've taken. - Payment & Completion:
msgPaymentConfirmedToClient,msgAppointmentCompletedToClient. - No-Show:
msgNoShowToClient. - Registration Invite:
msgRegistrationInvite, which can be appended to other messages to encourage clients to create an account. - Conversational Messaging:
msgNewMessageToClient,msgNewMessageToPro, which notify recipients of new messages in their chat threads.
Each template function takes specific options as parameters, allowing for dynamic content insertion (e.g., client name, date, time, professional name, or a link to the professional's slug). This structured approach simplifies message generation and ensures that all necessary information is included in a clear, readable format.
Database Interaction
The messages table is central to storing all WhatsApp communications. It captures the professionalId and clientId to link messages to specific conversations, along with sender (indicating if the message came from the professional or client), type (e.g., text), and the content itself. The fileUrl column is present, suggesting future capabilities for rich media messages. Timestamps like createdAt and readAt provide a complete audit trail of message flow and engagement.
Beyond the messages table, the WhatsApp integration interacts with several other tables to fulfill its functions:
professionals: Provides details likename,phone, andslugfor message personalization and linking.clients: Suppliesnameandphonefor addressing messages and identifying recipients.appointments: Crucial for linking reminders, confirmations, and cancellations to specific scheduled events. ThereminderSent24handreminderSent1hboolean columns in this table track whether these specific WhatsApp reminders have been dispatched.recurringSchedules: While not directly referenced inwhatsapp.ts, this table likely informs the creation ofappointmentsthat then trigger WhatsApp messages.
The notifications table, with its recipientId, recipientType, type, title, and body columns, could potentially be used to store a record of WhatsApp messages sent, or to trigger other forms of notifications based on WhatsApp events.
Design Decisions
The design of Odys's WhatsApp integration reflects several deliberate choices aimed at maintainability, consistency, and user experience:
- Centralized Message Definitions: Placing all message templates in
src/lib/whatsapp.tsis a clear decision to enforce consistency. It means that any change to a message's wording or structure only needs to happen in one place, reducing the risk of discrepancies and simplifying updates. This also makes it easier for developers to understand all possible outbound communications. - Evolution API for WhatsApp Business: The choice of Evolution API, as evidenced by
docker-compose.evolution.ymlandsrc/lib/whatsapp.ts, provides a self-hostable, flexible solution for interacting with the WhatsApp Business API. This allows Odys to manage its own infrastructure and potentially customize the API client's behavior, rather than relying on a fully managed third-party service. - Outbound Context Management: The
contextparameter insendWhatsAppand the subsequent call tosetOutboundContextaddresses a fundamental challenge of stateless APIs: maintaining conversational state. By temporarily storing theprofessionalIdandappointmentIdassociated with an outbound message, Odys can correctly attribute and route inbound replies, creating a more natural and efficient communication flow for both clients and professionals. - Transactional Message Formatting: The use of
transactionalHeaderandTRANSACTIONAL_FOOTERwith distinct rule lines is a thoughtful design choice. It visually differentiates automated system messages from direct human-to-human or AI-driven conversations within the same WhatsApp thread, improving clarity for the end-user. - Resilient
sendWhatsAppFunction: ThesendWhatsAppfunction's design to catch errors and return a boolean, rather than throwing exceptions, prioritizes system stability. This means that a temporary issue with the WhatsApp API or network connectivity won't crash the entire application, allowing other processes to continue while the communication failure is logged for later review.
Potential Improvements
- Internationalization of Message Templates: All 19 message templates in
src/lib/whatsapp.tsare currently hardcoded in Portuguese. As Odys expands, these messages will need to be localized. A concrete improvement would be to extract these strings into a dedicated i18n system (e.g., JSON files or a translation service) and modify the template functions to accept alocaleparameter, fetching the appropriate translated text. This would make the application adaptable to different languages without requiring code changes for each new locale. - Support for Rich Media Messages: The
messagestable includes afileUrlcolumn, indicating an architectural readiness for sending and receiving media. However, thesendWhatsAppfunction insrc/lib/whatsapp.tscurrently only supports sendingtext. ExtendingsendWhatsAppto handlefileUrlby calling the appropriate Evolution API endpoint (e.g.,sendImage,sendFile) would allow professionals to share documents, images, or other media with clients, enriching the communication experience. - Enhanced Phone Number Normalization: The
normalizeBrazilianPhonefunction insrc/lib/whatsapp.tsis specifically tailored for Brazilian phone numbers. While effective for the current context, if Odys plans to operate in other countries, this function would need to be generalized to handle various international phone number formats. This could involve integrating a more comprehensive phone number parsing library (e.g.,libphonenumber-js) or implementing logic to detect country codes beyond "55". - Granular Webhook Event Handling: The
docker-compose.evolution.ymlconfigures the Evolution API to only listen forMESSAGES_UPSERTevents via its webhook. While sufficient for basic message exchange, the Evolution API likely supports other events (e.g., message delivery receipts, read receipts, message status updates). Expanding the webhook configuration to include these events could provide valuable feedback to the Odys application, allowing for more sophisticated UI features (like "delivered" or "read" indicators) or more precise tracking of message engagement.
References
src/lib/whatsapp.tsdocker-compose.evolution.yml