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 +67 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +68 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +68 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
|
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 ((
|
|
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
|
|
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 = (
|
|
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)
|
|
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
|
});
|