Bringing an External CRM's Chats into Firestore for AI Search: Vector Search, Webhooks, and a Stubborn Bundling Error A developer integrated an external CRM's chat history into Firestore to power an AI assistant's semantic search, overcoming a recurring bundling error and a cursor bug. The solution reused existing functions, added server-side contact search with debouncing, and leveraged Firestore's native vector search for querying thousands of conversations. A client wanted their management panel's AI assistant to answer questions like "what did this contact write me" or "who replied about a certain topic", pulling from thousands of conversations held on an external CRM. Between the idea and the first correct answer there was a bundling error that came back three times, a cursor that never advanced, and a phone number that pointed to the wrong contact. The client's management panel already had a solid integration with the external CRM used to handle WhatsApp, SMS, and email conversations with contacts: reading sales pipelines, opening chat with bubbles and attachments, sending replies. All reusable infrastructure, already in production for a while. One specific piece was missing: no function queried the CRM's full contact directory — only opportunities inside a specific pipeline — and, more importantly, there was no way at all for the panel's AI assistant to read past conversations. First rule, before writing a single new line of code: reuse what already worked. The function that read multiple pipeline stages in parallel, the modal that opened chat with full history, the function that sent messages — all proven and already in use. The one genuinely missing piece was a function able to search the entire contact directory, with text search and pagination, which the existing functions built for single pipelines didn't do. With tens of thousands of contacts in the directory, loading them all client-side to filter isn't an option. The correct approach is server-side search: on every keystroke debounced by 400ms to avoid hammering the API , the backend function forwards the query to the CRM's own search engine, which runs it against the entire archive — name, phone, email — not just the results already shown on screen. What the user sees, 30 contacts at a time with a "load more" button, is only the current page, not the search scope. A real limitation worth knowing: a third-party CRM's search is almost always a "contains" match, not fuzzy — it doesn't tolerate typos or a swapped first/last name. Worth keeping in mind both in the UI and, later in this story, in a piece of AI logic that broke for exactly this reason. Before tackling the hard part, two minor fixes noticeably improved the contact directory's usability. The first was a misalignment between column headers and table rows: caused by using two different layout systems, a flex container for the header and a real table for the rows, which could never match in width. The fix was putting the header inside a real