wexa-chat 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -312,6 +312,73 @@ await chat.services.messages.sendMessage({
312
312
  });
313
313
  ```
314
314
 
315
+ ##### Email Threading Explained
316
+
317
+ Email threading works by maintaining a stable `thread_id` across all messages in the same conversation:
318
+
319
+ **How it works:**
320
+
321
+ 1. **First Email (New Thread)**
322
+ - Recruiter sends first email → Unipile returns `provider_id: "A"`
323
+ - Conversation stores `emailChatId = "A"` (the thread identifier)
324
+ - Message stores `metadata.email.providerId = "A"`
325
+
326
+ 2. **Candidate Replies**
327
+ - Webhook arrives with `thread_id: "A"` and new `provider_id: "B"`
328
+ - System finds conversation by `emailChatId = "A"`
329
+ - Message stores `metadata.email.providerId = "B"`
330
+
331
+ 3. **Recruiter Replies (Threading)**
332
+ - To continue the thread, pass `replyTo: "B"` (candidate's provider_id)
333
+ - Unipile returns new `provider_id: "C"`
334
+ - `emailChatId` remains `"A"` (NOT updated for replies)
335
+ - Message stores `metadata.email.providerId = "C"`
336
+
337
+ **Key Points:**
338
+ - `emailChatId` = Set ONCE on first email, used to find conversation
339
+ - `replyTo` = Previous message's `provider_id`, maintains email thread
340
+ - `providerId` = Current message's ID, stored for future replies
341
+
342
+ **Example: Reply to candidate's email**
343
+
344
+ ```ts
345
+ // 1. Get the last message from candidate
346
+ const messages = await chat.services.messages.listMessages({
347
+ organizationId: 'org-123',
348
+ conversationId: 'conv-456',
349
+ limit: 10,
350
+ });
351
+
352
+ const lastCandidateMsg = messages.items
353
+ .find(m => m.senderModel === 'Application');
354
+
355
+ // 2. Extract their providerId for threading
356
+ const replyToId = lastCandidateMsg?.metadata?.email?.providerId;
357
+
358
+ // 3. Send reply with replyTo
359
+ await chat.services.messages.sendMessage({
360
+ organizationId: 'org-123',
361
+ conversationId: 'conv-456',
362
+ senderModel: 'User',
363
+ senderId: 'user-1',
364
+ text: 'Great to hear from you! Let me provide more details...',
365
+ source: [sourceType.EMAIL],
366
+ connectorIds: {
367
+ email: { connectorId: 'em-connector-1', contactId: 'john@candidate.com' },
368
+ },
369
+ metadata: {
370
+ email: {
371
+ subject: 'Re: Job Opportunity',
372
+ recipientName: 'John Doe',
373
+ replyTo: replyToId, // ← This keeps emails in the same thread
374
+ }
375
+ }
376
+ });
377
+ ```
378
+
379
+ **Without `replyTo`:** Each email creates a new thread (separate email chains)
380
+ **With `replyTo`:** All emails stay in one thread (Gmail conversation view)
381
+
315
382
  #### Multi-Platform Messages
316
383
 
317
384
  ```ts
package/dist/index.d.cts CHANGED
@@ -176,6 +176,7 @@ interface IMessage extends Document {
176
176
  linkedin?: {
177
177
  subject?: string;
178
178
  isInMail?: boolean;
179
+ linkedinApi?: string;
179
180
  senderName?: string;
180
181
  senderProfileUrl?: string;
181
182
  };
@@ -276,6 +277,7 @@ type SendMessageArgs = {
276
277
  linkedin?: {
277
278
  subject?: string;
278
279
  isInMail?: boolean;
280
+ linkedinApi?: 'classic' | 'recruiter' | 'sales_navigator';
279
281
  };
280
282
  };
281
283
  };
package/dist/index.d.ts CHANGED
@@ -176,6 +176,7 @@ interface IMessage extends Document {
176
176
  linkedin?: {
177
177
  subject?: string;
178
178
  isInMail?: boolean;
179
+ linkedinApi?: string;
179
180
  senderName?: string;
180
181
  senderProfileUrl?: string;
181
182
  };
@@ -276,6 +277,7 @@ type SendMessageArgs = {
276
277
  linkedin?: {
277
278
  subject?: string;
278
279
  isInMail?: boolean;
280
+ linkedinApi?: 'classic' | 'recruiter' | 'sales_navigator';
279
281
  };
280
282
  };
281
283
  };
package/dist/index.js CHANGED
@@ -566,6 +566,7 @@ function createMessagesService(models, hooks = {}) {
566
566
  * @returns The created message
567
567
  */
568
568
  async sendMessage(args) {
569
+ var _a, _b;
569
570
  const {
570
571
  organizationId,
571
572
  conversationId,
@@ -593,32 +594,67 @@ function createMessagesService(models, hooks = {}) {
593
594
  const linkedinMeta = metadata == null ? void 0 : metadata.linkedin;
594
595
  lastLinkedInId = LinkedinConnector == null ? void 0 : LinkedinConnector.connectorId;
595
596
  if (LinkedinConnector) {
596
- tasks.push({
597
- name: "linkedin",
598
- run: async () => {
599
- const path = paths.linkedin.startChat(String(LinkedinConnector.connectorId));
600
- const payload = {
601
- linkedin_url: LinkedinConnector.contactId,
602
- text
603
- };
604
- if (linkedinMeta == null ? void 0 : linkedinMeta.subject) {
605
- payload.subject = linkedinMeta.subject;
606
- }
607
- if (linkedinMeta == null ? void 0 : linkedinMeta.isInMail) {
608
- payload.linkedin_inmail = linkedinMeta.isInMail;
609
- }
610
- console.log("LinkedIn payload:", payload);
611
- try {
612
- const res = await getAxios().post(path, payload);
613
- const data = res.data;
614
- console.log("linkedinChatId", data);
615
- linkedinChatId = data.chat_data.chat_id;
616
- return { status: "fulfilled", value: data };
617
- } catch (error) {
618
- return { status: "rejected", reason: error };
619
- }
597
+ if (linkedinMeta == null ? void 0 : linkedinMeta.isInMail) {
598
+ const missingFields = [];
599
+ if (!linkedinMeta.subject) missingFields.push("subject");
600
+ if (!linkedinMeta.linkedinApi) missingFields.push("linkedinApi");
601
+ if (missingFields.length > 0) {
602
+ failed_source.push({
603
+ source: "linkedin",
604
+ message: `InMail requires the following fields: ${missingFields.join(", ")}`
605
+ });
606
+ } else {
607
+ tasks.push({
608
+ name: "linkedin",
609
+ run: async () => {
610
+ const path = paths.linkedin.startChat(String(LinkedinConnector.connectorId));
611
+ const payload = {
612
+ linkedin_url: LinkedinConnector.contactId,
613
+ text
614
+ };
615
+ if (linkedinMeta.subject) {
616
+ payload.subject = linkedinMeta.subject;
617
+ }
618
+ if (linkedinMeta.linkedinApi) {
619
+ payload.linkedin_api = linkedinMeta.linkedinApi;
620
+ }
621
+ if (linkedinMeta.isInMail) {
622
+ payload.linkedin_inmail = linkedinMeta.isInMail;
623
+ }
624
+ try {
625
+ const res = await getAxios().post(path, payload);
626
+ const data = res.data;
627
+ linkedinChatId = data.chat_data.chat_id;
628
+ return { status: "fulfilled", value: data };
629
+ } catch (error) {
630
+ return { status: "rejected", reason: error };
631
+ }
632
+ }
633
+ });
620
634
  }
621
- });
635
+ } else {
636
+ tasks.push({
637
+ name: "linkedin",
638
+ run: async () => {
639
+ const path = paths.linkedin.startChat(String(LinkedinConnector.connectorId));
640
+ const payload = {
641
+ linkedin_url: LinkedinConnector.contactId,
642
+ text
643
+ };
644
+ if (linkedinMeta == null ? void 0 : linkedinMeta.linkedinApi) {
645
+ payload.linkedin_api = linkedinMeta.linkedinApi;
646
+ }
647
+ try {
648
+ const res = await getAxios().post(path, payload);
649
+ const data = res.data;
650
+ linkedinChatId = data.chat_data.chat_id;
651
+ return { status: "fulfilled", value: data };
652
+ } catch (error) {
653
+ return { status: "rejected", reason: error };
654
+ }
655
+ }
656
+ });
657
+ }
622
658
  } else {
623
659
  failed_source.push({ source: "linkedin", message: "Missing connector configuration" });
624
660
  }
@@ -659,7 +695,7 @@ function createMessagesService(models, hooks = {}) {
659
695
  tasks.push({
660
696
  name: "email",
661
697
  run: async () => {
662
- var _a;
698
+ var _a2;
663
699
  const path = paths.email.sendEmail(String(emailConnector.connectorId));
664
700
  const primaryRecipient = {
665
701
  display_name: (emailMeta == null ? void 0 : emailMeta.recipientName) || "Recipient",
@@ -712,7 +748,7 @@ function createMessagesService(models, hooks = {}) {
712
748
  const res = await getAxios().post(path, payload);
713
749
  console.log("Email response:", res.data);
714
750
  const data = res.data;
715
- if ((_a = data.mail_sent_data) == null ? void 0 : _a.provider_id) {
751
+ if ((_a2 = data.mail_sent_data) == null ? void 0 : _a2.provider_id) {
716
752
  emailChatId = data.mail_sent_data.provider_id;
717
753
  console.log("Email provider_id extracted:", emailChatId);
718
754
  } else {
@@ -731,12 +767,12 @@ function createMessagesService(models, hooks = {}) {
731
767
  if (tasks.length > 0) {
732
768
  const results = await Promise.allSettled(tasks.map((t) => t.run()));
733
769
  results.forEach((result, idx) => {
734
- var _a, _b, _c, _d, _e, _f;
770
+ var _a2, _b2, _c, _d, _e, _f;
735
771
  const name = tasks[idx].name;
736
772
  if (result.status === "fulfilled") {
737
773
  success_source.push(name);
738
774
  } else {
739
- const error = ((_c = (_b = (_a = result.reason) == null ? void 0 : _a.response) == null ? void 0 : _b.data) == null ? void 0 : _c.detail) || ((_e = (_d = result.reason) == null ? void 0 : _d.response) == null ? void 0 : _e.data) || ((_f = result.reason) == null ? void 0 : _f.message) || "Unknown error";
775
+ const error = ((_c = (_b2 = (_a2 = result.reason) == null ? void 0 : _a2.response) == null ? void 0 : _b2.data) == null ? void 0 : _c.detail) || ((_e = (_d = result.reason) == null ? void 0 : _d.response) == null ? void 0 : _e.data) || ((_f = result.reason) == null ? void 0 : _f.message) || "Unknown error";
740
776
  failed_source.push({
741
777
  source: name,
742
778
  message: typeof error === "string" ? error : JSON.stringify(error)
@@ -779,7 +815,9 @@ function createMessagesService(models, hooks = {}) {
779
815
  if (lastEmailId) conversationUpdate.lastEmailId = lastEmailId;
780
816
  if (linkedinChatId) conversationUpdate.linkedinChatId = linkedinChatId;
781
817
  if (whatsappChatId) conversationUpdate.whatsappChatId = whatsappChatId;
782
- if (emailChatId) conversationUpdate.emailChatId = emailChatId;
818
+ if (emailChatId && !((_b = (_a = args.metadata) == null ? void 0 : _a.email) == null ? void 0 : _b.replyTo)) {
819
+ conversationUpdate.emailChatId = emailChatId;
820
+ }
783
821
  await Conversation.findByIdAndUpdate(conversationId, {
784
822
  $set: conversationUpdate
785
823
  });