@warriorteam/redai-zalo-sdk 1.2.0 → 1.4.0

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.
Files changed (77) hide show
  1. package/README.md +563 -550
  2. package/dist/index.d.ts +0 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -5
  5. package/dist/index.js.map +1 -1
  6. package/dist/services/article.service.d.ts +1 -1
  7. package/dist/services/article.service.d.ts.map +1 -1
  8. package/dist/services/article.service.js +24 -16
  9. package/dist/services/article.service.js.map +1 -1
  10. package/dist/services/auth.service.d.ts +1 -0
  11. package/dist/services/auth.service.d.ts.map +1 -1
  12. package/dist/services/auth.service.js +23 -9
  13. package/dist/services/auth.service.js.map +1 -1
  14. package/dist/services/consultation.service.d.ts +63 -16
  15. package/dist/services/consultation.service.d.ts.map +1 -1
  16. package/dist/services/consultation.service.js +264 -49
  17. package/dist/services/consultation.service.js.map +1 -1
  18. package/dist/services/general-message.service.d.ts +2 -25
  19. package/dist/services/general-message.service.d.ts.map +1 -1
  20. package/dist/services/general-message.service.js +11 -112
  21. package/dist/services/general-message.service.js.map +1 -1
  22. package/dist/services/group-management.service.d.ts +1 -1
  23. package/dist/services/group-management.service.d.ts.map +1 -1
  24. package/dist/services/group-management.service.js +59 -27
  25. package/dist/services/group-management.service.js.map +1 -1
  26. package/dist/services/group-message.service.d.ts +1 -1
  27. package/dist/services/group-message.service.d.ts.map +1 -1
  28. package/dist/services/group-message.service.js +49 -23
  29. package/dist/services/group-message.service.js.map +1 -1
  30. package/dist/services/message-management.service.d.ts +21 -2
  31. package/dist/services/message-management.service.d.ts.map +1 -1
  32. package/dist/services/message-management.service.js +83 -7
  33. package/dist/services/message-management.service.js.map +1 -1
  34. package/dist/services/oa.service.d.ts +1 -0
  35. package/dist/services/oa.service.d.ts.map +1 -1
  36. package/dist/services/oa.service.js +23 -5
  37. package/dist/services/oa.service.js.map +1 -1
  38. package/dist/services/user.service.d.ts +108 -18
  39. package/dist/services/user.service.d.ts.map +1 -1
  40. package/dist/services/user.service.js +260 -59
  41. package/dist/services/user.service.js.map +1 -1
  42. package/dist/services/video-upload.service.d.ts +1 -1
  43. package/dist/services/video-upload.service.d.ts.map +1 -1
  44. package/dist/services/video-upload.service.js +11 -8
  45. package/dist/services/video-upload.service.js.map +1 -1
  46. package/dist/services/zns.service.d.ts +88 -21
  47. package/dist/services/zns.service.d.ts.map +1 -1
  48. package/dist/services/zns.service.js +157 -42
  49. package/dist/services/zns.service.js.map +1 -1
  50. package/dist/types/group.d.ts +5 -5
  51. package/dist/types/group.d.ts.map +1 -1
  52. package/dist/types/user.d.ts +155 -12
  53. package/dist/types/user.d.ts.map +1 -1
  54. package/dist/types/user.js.map +1 -1
  55. package/dist/types/zns.d.ts +67 -33
  56. package/dist/types/zns.d.ts.map +1 -1
  57. package/dist/zalo-sdk.d.ts +3 -11
  58. package/dist/zalo-sdk.d.ts.map +1 -1
  59. package/dist/zalo-sdk.js +0 -10
  60. package/dist/zalo-sdk.js.map +1 -1
  61. package/docs/API_REFERENCE.md +680 -0
  62. package/docs/AUTHENTICATION.md +709 -0
  63. package/docs/CONSULTATION_SERVICE.md +512 -330
  64. package/docs/GROUP_MANAGEMENT.md +2 -2
  65. package/docs/MESSAGE_SERVICES.md +1224 -0
  66. package/docs/TAG_MANAGEMENT.md +1462 -0
  67. package/docs/USER_MANAGEMENT.md +481 -0
  68. package/docs/ZNS_SERVICE.md +985 -0
  69. package/package.json +1 -1
  70. package/dist/services/tag.service.d.ts +0 -144
  71. package/dist/services/tag.service.d.ts.map +0 -1
  72. package/dist/services/tag.service.js +0 -184
  73. package/dist/services/tag.service.js.map +0 -1
  74. package/dist/services/user-management.service.d.ts +0 -117
  75. package/dist/services/user-management.service.d.ts.map +0 -1
  76. package/dist/services/user-management.service.js +0 -239
  77. package/dist/services/user-management.service.js.map +0 -1
@@ -0,0 +1,1462 @@
1
+ # RedAI Zalo SDK - Tag Management Guide
2
+
3
+ ## Tổng quan
4
+
5
+ **Tag Management** là hệ thống phân loại và quản lý người dùng thông qua các nhãn (tags). Điều này cho phép:
6
+
7
+ - 🏷️ **User Segmentation** - Phân đoạn khách hàng theo nhiều tiêu chí
8
+ - 📊 **Targeted Marketing** - Gửi tin nhắn có mục tiêu cụ thể
9
+ - 🎯 **Personalized Content** - Nội dung được cá nhân hóa theo tags
10
+ - 📈 **Analytics & Insights** - Phân tích hiệu quả theo từng segment
11
+ - 🔄 **Automation** - Tự động gán tags dựa trên hành vi
12
+
13
+ ---
14
+
15
+ ## TagService
16
+
17
+ Service chính để quản lý tags và tag users.
18
+
19
+ ### Khởi tạo
20
+
21
+ ```typescript
22
+ import { ZaloSDK } from "@warriorteam/redai-zalo-sdk";
23
+
24
+ const zalo = new ZaloSDK({
25
+ appId: "your-app-id",
26
+ appSecret: "your-app-secret"
27
+ });
28
+
29
+ // Access tag service
30
+ const tagService = zalo.tag;
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Quản lý Tags
36
+
37
+ ### 1. Tạo tag mới
38
+
39
+ ```typescript
40
+ // Tạo tag mới cho campaign
41
+ const newTag = await zalo.tag.createTag(
42
+ accessToken,
43
+ "BLACK_FRIDAY_2024"
44
+ );
45
+
46
+ console.log("Created tag:", newTag.tag_id);
47
+ console.log("Tag name:", newTag.tag_name);
48
+ ```
49
+
50
+ ### 2. Lấy danh sách tags
51
+
52
+ ```typescript
53
+ // Lấy tất cả tags hiện có
54
+ const tagList = await zalo.tag.getTagList(accessToken);
55
+
56
+ console.log("Total tags:", tagList.total);
57
+ tagList.data.forEach(tag => {
58
+ console.log(`${tag.tag_name}: ${tag.user_count} users`);
59
+ });
60
+ ```
61
+
62
+ ### 3. Xóa tag
63
+
64
+ ```typescript
65
+ // Xóa tag không còn sử dụng
66
+ await zalo.tag.deleteTag(accessToken, "OLD_CAMPAIGN_TAG");
67
+ console.log("Tag deleted successfully");
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Gán Tags cho Users
73
+
74
+ ### 1. Tag một user
75
+
76
+ ```typescript
77
+ // Gán nhiều tags cho một user
78
+ await zalo.tag.tagUser(
79
+ accessToken,
80
+ "user-zalo-id",
81
+ ["VIP_CUSTOMER", "ELECTRONICS_BUYER", "PREMIUM"]
82
+ );
83
+
84
+ console.log("User tagged successfully");
85
+ ```
86
+
87
+ ### 2. Untag user
88
+
89
+ ```typescript
90
+ // Bỏ tag khỏi user
91
+ await zalo.tag.untagUser(
92
+ accessToken,
93
+ "user-zalo-id",
94
+ ["OLD_CAMPAIGN", "EXPIRED_SEGMENT"]
95
+ );
96
+
97
+ console.log("Tags removed successfully");
98
+ ```
99
+
100
+ ### 3. Lấy tags của user
101
+
102
+ ```typescript
103
+ // Xem tất cả tags của user
104
+ const userTags = await zalo.tag.getUserTags(
105
+ accessToken,
106
+ "user-zalo-id"
107
+ );
108
+
109
+ console.log("User tags:", userTags.map(tag => tag.tag_name));
110
+ ```
111
+
112
+ ### 4. Lấy users theo tag
113
+
114
+ ```typescript
115
+ // Lấy tất cả users có tag cụ thể
116
+ const taggedUsers = await zalo.tag.getUsersByTag(
117
+ accessToken,
118
+ "VIP_CUSTOMER",
119
+ {
120
+ offset: 0,
121
+ limit: 50
122
+ }
123
+ );
124
+
125
+ console.log(`Found ${taggedUsers.total} VIP customers`);
126
+ taggedUsers.data.forEach(user => {
127
+ console.log(`${user.display_name} - ${user.user_id}`);
128
+ });
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Tag Strategies & Use Cases
134
+
135
+ ### 1. Customer Lifecycle Tags
136
+
137
+ ```typescript
138
+ class CustomerLifecycleTagging {
139
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
140
+
141
+ async tagUserByLifecycleStage(userId: string, stage: LifecycleStage) {
142
+ // Remove old lifecycle tags
143
+ const currentTags = await this.zalo.tag.getUserTags(this.accessToken, userId);
144
+ const lifecycleTags = currentTags.filter(tag =>
145
+ tag.tag_name.startsWith('LIFECYCLE_')
146
+ ).map(tag => tag.tag_name);
147
+
148
+ if (lifecycleTags.length > 0) {
149
+ await this.zalo.tag.untagUser(this.accessToken, userId, lifecycleTags);
150
+ }
151
+
152
+ // Add new lifecycle tag
153
+ const newTag = `LIFECYCLE_${stage.toUpperCase()}`;
154
+ await this.zalo.tag.tagUser(this.accessToken, userId, [newTag]);
155
+
156
+ // Add complementary tags based on stage
157
+ const complementaryTags = this.getComplementaryTags(stage);
158
+ if (complementaryTags.length > 0) {
159
+ await this.zalo.tag.tagUser(this.accessToken, userId, complementaryTags);
160
+ }
161
+ }
162
+
163
+ private getComplementaryTags(stage: LifecycleStage): string[] {
164
+ const tagMappings = {
165
+ 'new_lead': ['NURTURING_NEEDED', 'WELCOME_SERIES'],
166
+ 'qualified_lead': ['CONSIDERATION_STAGE', 'SALES_READY'],
167
+ 'customer': ['ONBOARDING_NEEDED', 'CROSS_SELL_ELIGIBLE'],
168
+ 'repeat_customer': ['LOYALTY_PROGRAM', 'REFERRAL_CANDIDATE'],
169
+ 'champion': ['VIP_TREATMENT', 'TESTIMONIAL_CANDIDATE'],
170
+ 'at_risk': ['RETENTION_CAMPAIGN', 'WIN_BACK_NEEDED'],
171
+ 'churned': ['RE_ENGAGEMENT', 'FEEDBACK_SURVEY']
172
+ };
173
+
174
+ return tagMappings[stage] || [];
175
+ }
176
+
177
+ async autoTagBasedOnPurchaseHistory(userId: string) {
178
+ const userAnalytics = await this.getUserPurchaseAnalytics(userId);
179
+ const tagsToAdd = [];
180
+
181
+ // Value-based tags
182
+ if (userAnalytics.totalSpent > 50000000) {
183
+ tagsToAdd.push('HIGH_VALUE_CUSTOMER');
184
+ } else if (userAnalytics.totalSpent > 10000000) {
185
+ tagsToAdd.push('MEDIUM_VALUE_CUSTOMER');
186
+ }
187
+
188
+ // Frequency-based tags
189
+ if (userAnalytics.purchaseCount > 10) {
190
+ tagsToAdd.push('FREQUENT_BUYER');
191
+ }
192
+
193
+ // Recency-based tags
194
+ const daysSinceLastPurchase = userAnalytics.daysSinceLastPurchase;
195
+ if (daysSinceLastPurchase > 90) {
196
+ tagsToAdd.push('INACTIVE_BUYER');
197
+ } else if (daysSinceLastPurchase <= 30) {
198
+ tagsToAdd.push('ACTIVE_BUYER');
199
+ }
200
+
201
+ // Category preferences
202
+ const topCategory = userAnalytics.topPurchaseCategory;
203
+ if (topCategory) {
204
+ tagsToAdd.push(`CATEGORY_${topCategory.toUpperCase()}`);
205
+ }
206
+
207
+ if (tagsToAdd.length > 0) {
208
+ await this.zalo.tag.tagUser(this.accessToken, userId, tagsToAdd);
209
+ }
210
+ }
211
+ }
212
+ ```
213
+
214
+ ### 2. Behavioral Tags
215
+
216
+ ```typescript
217
+ class BehavioralTagging {
218
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
219
+
220
+ async tagUsersByEngagement() {
221
+ const users = await this.getAllActiveUsers();
222
+
223
+ for (const user of users) {
224
+ const engagementLevel = await this.calculateEngagementLevel(user.user_id);
225
+ await this.applyEngagementTags(user.user_id, engagementLevel);
226
+ }
227
+ }
228
+
229
+ private async calculateEngagementLevel(userId: string): Promise<EngagementLevel> {
230
+ const interactions = await this.zalo.userManagement.getUserInteractions(
231
+ this.accessToken,
232
+ userId,
233
+ {
234
+ from_time: Date.now() - (30 * 24 * 60 * 60 * 1000), // 30 days
235
+ limit: 1000
236
+ }
237
+ );
238
+
239
+ const messagesSent = interactions.filter(i => i.from_user).length;
240
+ const messagesReceived = interactions.filter(i => !i.from_user).length;
241
+ const avgResponseTime = this.calculateAverageResponseTime(interactions);
242
+
243
+ const score = this.calculateEngagementScore(
244
+ messagesSent,
245
+ messagesReceived,
246
+ avgResponseTime
247
+ );
248
+
249
+ if (score > 80) return 'very_high';
250
+ if (score > 60) return 'high';
251
+ if (score > 40) return 'medium';
252
+ if (score > 20) return 'low';
253
+ return 'very_low';
254
+ }
255
+
256
+ private async applyEngagementTags(userId: string, level: EngagementLevel) {
257
+ // Remove old engagement tags
258
+ const engagementTags = [
259
+ 'ENGAGEMENT_VERY_HIGH',
260
+ 'ENGAGEMENT_HIGH',
261
+ 'ENGAGEMENT_MEDIUM',
262
+ 'ENGAGEMENT_LOW',
263
+ 'ENGAGEMENT_VERY_LOW'
264
+ ];
265
+
266
+ const currentTags = await this.zalo.tag.getUserTags(this.accessToken, userId);
267
+ const oldEngagementTags = currentTags
268
+ .filter(tag => engagementTags.includes(tag.tag_name))
269
+ .map(tag => tag.tag_name);
270
+
271
+ if (oldEngagementTags.length > 0) {
272
+ await this.zalo.tag.untagUser(this.accessToken, userId, oldEngagementTags);
273
+ }
274
+
275
+ // Add new engagement tag
276
+ const newTag = `ENGAGEMENT_${level.toUpperCase()}`;
277
+ await this.zalo.tag.tagUser(this.accessToken, userId, [newTag]);
278
+
279
+ // Add action-based tags
280
+ const actionTags = this.getActionBasedTags(level);
281
+ if (actionTags.length > 0) {
282
+ await this.zalo.tag.tagUser(this.accessToken, userId, actionTags);
283
+ }
284
+ }
285
+
286
+ private getActionBasedTags(level: EngagementLevel): string[] {
287
+ const actionMappings = {
288
+ 'very_high': ['BRAND_ADVOCATE', 'REFERRAL_READY', 'BETA_TESTER'],
289
+ 'high': ['LOYAL_CUSTOMER', 'UPSELL_READY'],
290
+ 'medium': ['NURTURING_NEEDED', 'ENGAGEMENT_BOOST'],
291
+ 'low': ['RE_ENGAGEMENT_NEEDED'],
292
+ 'very_low': ['CHURN_RISK', 'WIN_BACK_CAMPAIGN']
293
+ };
294
+
295
+ return actionMappings[level] || [];
296
+ }
297
+ }
298
+ ```
299
+
300
+ ### 3. Campaign Tags
301
+
302
+ ```typescript
303
+ class CampaignTagging {
304
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
305
+
306
+ // Tạo campaign tags tự động
307
+ async createCampaignTags(campaign: Campaign) {
308
+ const campaignTags = [
309
+ `CAMPAIGN_${campaign.id.toUpperCase()}`,
310
+ `CAMPAIGN_${campaign.type.toUpperCase()}`,
311
+ `CAMPAIGN_${campaign.startDate.getFullYear()}_${campaign.startDate.getMonth() + 1}`
312
+ ];
313
+
314
+ for (const tagName of campaignTags) {
315
+ try {
316
+ await this.zalo.tag.createTag(this.accessToken, tagName);
317
+ console.log(`Created campaign tag: ${tagName}`);
318
+ } catch (error) {
319
+ if (error.code !== -224) { // Tag already exists
320
+ console.error(`Failed to create tag ${tagName}:`, error);
321
+ }
322
+ }
323
+ }
324
+
325
+ return campaignTags;
326
+ }
327
+
328
+ // Tag users dựa trên response với campaign
329
+ async tagCampaignResponders(campaignId: string) {
330
+ const campaignMessages = await this.getCampaignMessages(campaignId);
331
+ const responders = new Set<string>();
332
+ const nonResponders = new Set<string>();
333
+
334
+ // Identify responders vs non-responders
335
+ for (const message of campaignMessages) {
336
+ const hasResponse = await this.checkUserResponse(message.user_id, message.sent_time);
337
+
338
+ if (hasResponse) {
339
+ responders.add(message.user_id);
340
+ } else {
341
+ nonResponders.add(message.user_id);
342
+ }
343
+ }
344
+
345
+ // Tag responders
346
+ const responderTag = `CAMPAIGN_${campaignId}_RESPONDER`;
347
+ for (const userId of responders) {
348
+ await this.zalo.tag.tagUser(this.accessToken, userId, [responderTag]);
349
+ }
350
+
351
+ // Tag non-responders for follow-up
352
+ const nonResponderTag = `CAMPAIGN_${campaignId}_NON_RESPONDER`;
353
+ for (const userId of nonResponders) {
354
+ await this.zalo.tag.tagUser(this.accessToken, userId, [nonResponderTag]);
355
+ }
356
+
357
+ return {
358
+ responders: responders.size,
359
+ nonResponders: nonResponders.size,
360
+ responseRate: (responders.size / (responders.size + nonResponders.size) * 100).toFixed(2) + '%'
361
+ };
362
+ }
363
+
364
+ // Auto cleanup expired campaign tags
365
+ async cleanupExpiredCampaignTags() {
366
+ const tagList = await this.zalo.tag.getTagList(this.accessToken);
367
+ const campaignTags = tagList.data.filter(tag =>
368
+ tag.tag_name.startsWith('CAMPAIGN_')
369
+ );
370
+
371
+ for (const tag of campaignTags) {
372
+ const isExpired = await this.isCampaignExpired(tag.tag_name);
373
+
374
+ if (isExpired && tag.user_count === 0) {
375
+ await this.zalo.tag.deleteTag(this.accessToken, tag.tag_name);
376
+ console.log(`Deleted expired campaign tag: ${tag.tag_name}`);
377
+ } else if (isExpired) {
378
+ // Move users to archived tag
379
+ const archivedTag = `ARCHIVED_${tag.tag_name}`;
380
+ await this.zalo.tag.createTag(this.accessToken, archivedTag);
381
+
382
+ const users = await this.zalo.tag.getUsersByTag(this.accessToken, tag.tag_name);
383
+ for (const user of users.data) {
384
+ await this.zalo.tag.tagUser(this.accessToken, user.user_id, [archivedTag]);
385
+ await this.zalo.tag.untagUser(this.accessToken, user.user_id, [tag.tag_name]);
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Advanced Tag Operations
396
+
397
+ ### 1. Bulk Tag Operations
398
+
399
+ ```typescript
400
+ class BulkTagOperations {
401
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
402
+
403
+ async bulkTagUsers(
404
+ userIds: string[],
405
+ tagsToAdd: string[],
406
+ tagsToRemove: string[] = []
407
+ ): Promise<BulkTagResult> {
408
+ const results = {
409
+ successful: 0,
410
+ failed: 0,
411
+ errors: [] as Array<{userId: string, error: string}>
412
+ };
413
+
414
+ const batchSize = 5; // Process 5 users at a time to avoid rate limits
415
+
416
+ for (let i = 0; i < userIds.length; i += batchSize) {
417
+ const batch = userIds.slice(i, i + batchSize);
418
+
419
+ const promises = batch.map(async (userId) => {
420
+ try {
421
+ // Remove tags first
422
+ if (tagsToRemove.length > 0) {
423
+ await this.zalo.tag.untagUser(this.accessToken, userId, tagsToRemove);
424
+ }
425
+
426
+ // Add new tags
427
+ if (tagsToAdd.length > 0) {
428
+ await this.zalo.tag.tagUser(this.accessToken, userId, tagsToAdd);
429
+ }
430
+
431
+ return { userId, success: true };
432
+ } catch (error) {
433
+ return { userId, success: false, error: error.message };
434
+ }
435
+ });
436
+
437
+ const batchResults = await Promise.all(promises);
438
+
439
+ batchResults.forEach(result => {
440
+ if (result.success) {
441
+ results.successful++;
442
+ } else {
443
+ results.failed++;
444
+ results.errors.push({
445
+ userId: result.userId,
446
+ error: result.error || 'Unknown error'
447
+ });
448
+ }
449
+ });
450
+
451
+ // Delay between batches
452
+ if (i + batchSize < userIds.length) {
453
+ await this.delay(1000);
454
+ }
455
+ }
456
+
457
+ return results;
458
+ }
459
+
460
+ async conditionalBulkTagging(
461
+ condition: TagCondition,
462
+ tagsToApply: string[]
463
+ ): Promise<BulkTagResult> {
464
+ const matchingUsers = await this.findUsersMatchingCondition(condition);
465
+
466
+ console.log(`Found ${matchingUsers.length} users matching condition`);
467
+
468
+ return this.bulkTagUsers(
469
+ matchingUsers.map(u => u.user_id),
470
+ tagsToApply
471
+ );
472
+ }
473
+
474
+ private async findUsersMatchingCondition(condition: TagCondition): Promise<UserProfile[]> {
475
+ const allUsers = await this.getAllUsers();
476
+ const matchingUsers = [];
477
+
478
+ for (const user of allUsers) {
479
+ const matches = await this.evaluateCondition(user, condition);
480
+ if (matches) {
481
+ matchingUsers.push(user);
482
+ }
483
+ }
484
+
485
+ return matchingUsers;
486
+ }
487
+
488
+ private async evaluateCondition(user: UserProfile, condition: TagCondition): Promise<boolean> {
489
+ switch (condition.type) {
490
+ case 'has_tags':
491
+ const userTags = await this.zalo.tag.getUserTags(this.accessToken, user.user_id);
492
+ const userTagNames = userTags.map(tag => tag.tag_name);
493
+ return condition.tags.every(tag => userTagNames.includes(tag));
494
+
495
+ case 'missing_tags':
496
+ const currentTags = await this.zalo.tag.getUserTags(this.accessToken, user.user_id);
497
+ const currentTagNames = currentTags.map(tag => tag.tag_name);
498
+ return condition.tags.some(tag => !currentTagNames.includes(tag));
499
+
500
+ case 'purchase_amount':
501
+ const analytics = await this.getUserAnalytics(user.user_id);
502
+ return this.evaluateNumericCondition(analytics.totalSpent, condition.operator, condition.value);
503
+
504
+ case 'last_interaction':
505
+ const lastInteraction = await this.getLastInteractionDays(user.user_id);
506
+ return this.evaluateNumericCondition(lastInteraction, condition.operator, condition.value);
507
+
508
+ default:
509
+ return false;
510
+ }
511
+ }
512
+
513
+ private delay(ms: number): Promise<void> {
514
+ return new Promise(resolve => setTimeout(resolve, ms));
515
+ }
516
+ }
517
+ ```
518
+
519
+ ### 2. Smart Tag Suggestions
520
+
521
+ ```typescript
522
+ class SmartTagSuggestions {
523
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
524
+
525
+ async suggestTagsForUser(userId: string): Promise<TagSuggestion[]> {
526
+ const userProfile = await this.zalo.userManagement.getUserProfile(
527
+ this.accessToken,
528
+ userId
529
+ );
530
+
531
+ const userInteractions = await this.zalo.userManagement.getUserInteractions(
532
+ this.accessToken,
533
+ userId,
534
+ { limit: 500 }
535
+ );
536
+
537
+ const suggestions = [];
538
+
539
+ // Behavioral suggestions
540
+ const behaviorSuggestions = await this.suggestBehavioralTags(userInteractions);
541
+ suggestions.push(...behaviorSuggestions);
542
+
543
+ // Value-based suggestions
544
+ const valueSuggestions = await this.suggestValueBasedTags(userInteractions);
545
+ suggestions.push(...valueSuggestions);
546
+
547
+ // Interest-based suggestions
548
+ const interestSuggestions = await this.suggestInterestBasedTags(userInteractions);
549
+ suggestions.push(...interestSuggestions);
550
+
551
+ // Lifecycle suggestions
552
+ const lifecycleSuggestions = await this.suggestLifecycleTags(userProfile, userInteractions);
553
+ suggestions.push(...lifecycleSuggestions);
554
+
555
+ return suggestions.sort((a, b) => b.confidence - a.confidence).slice(0, 10);
556
+ }
557
+
558
+ private async suggestBehavioralTags(interactions: any[]): Promise<TagSuggestion[]> {
559
+ const suggestions = [];
560
+
561
+ // Analyze response patterns
562
+ const responseTime = this.calculateAverageResponseTime(interactions);
563
+ if (responseTime < 300000) { // 5 minutes
564
+ suggestions.push({
565
+ tag: 'QUICK_RESPONDER',
566
+ confidence: 0.9,
567
+ reason: 'User responds quickly to messages'
568
+ });
569
+ }
570
+
571
+ // Analyze interaction frequency
572
+ const monthlyInteractions = this.countInteractionsInPeriod(interactions, 30);
573
+ if (monthlyInteractions > 20) {
574
+ suggestions.push({
575
+ tag: 'HIGHLY_ENGAGED',
576
+ confidence: 0.8,
577
+ reason: 'High interaction frequency'
578
+ });
579
+ }
580
+
581
+ // Analyze message types
582
+ const questionCount = interactions.filter(i =>
583
+ i.message && i.message.includes('?')
584
+ ).length;
585
+
586
+ if (questionCount > interactions.length * 0.3) {
587
+ suggestions.push({
588
+ tag: 'INQUISITIVE',
589
+ confidence: 0.7,
590
+ reason: 'Asks many questions'
591
+ });
592
+ }
593
+
594
+ return suggestions;
595
+ }
596
+
597
+ private async suggestValueBasedTags(interactions: any[]): Promise<TagSuggestion[]> {
598
+ const suggestions = [];
599
+
600
+ const purchases = interactions.filter(i => i.type === 'purchase');
601
+ if (purchases.length === 0) return suggestions;
602
+
603
+ const totalValue = purchases.reduce((sum, p) => sum + (p.value || 0), 0);
604
+ const avgOrderValue = totalValue / purchases.length;
605
+
606
+ if (totalValue > 50000000) {
607
+ suggestions.push({
608
+ tag: 'HIGH_VALUE_CUSTOMER',
609
+ confidence: 0.95,
610
+ reason: `Total purchases: ${this.formatCurrency(totalValue)}`
611
+ });
612
+ } else if (totalValue > 10000000) {
613
+ suggestions.push({
614
+ tag: 'MEDIUM_VALUE_CUSTOMER',
615
+ confidence: 0.85,
616
+ reason: `Total purchases: ${this.formatCurrency(totalValue)}`
617
+ });
618
+ }
619
+
620
+ if (avgOrderValue > 5000000) {
621
+ suggestions.push({
622
+ tag: 'PREMIUM_BUYER',
623
+ confidence: 0.8,
624
+ reason: `Average order: ${this.formatCurrency(avgOrderValue)}`
625
+ });
626
+ }
627
+
628
+ return suggestions;
629
+ }
630
+
631
+ async autoApplySuggestedTags(userId: string, minConfidence: number = 0.8) {
632
+ const suggestions = await this.suggestTagsForUser(userId);
633
+ const highConfidenceTags = suggestions
634
+ .filter(s => s.confidence >= minConfidence)
635
+ .map(s => s.tag);
636
+
637
+ if (highConfidenceTags.length > 0) {
638
+ await this.zalo.tag.tagUser(this.accessToken, userId, highConfidenceTags);
639
+
640
+ console.log(`Auto-applied ${highConfidenceTags.length} tags to user ${userId}:`);
641
+ highConfidenceTags.forEach(tag => console.log(` - ${tag}`));
642
+ }
643
+ }
644
+ }
645
+ ```
646
+
647
+ ---
648
+
649
+ ## Tag Analytics & Insights
650
+
651
+ ### 1. Tag Performance Analytics
652
+
653
+ ```typescript
654
+ class TagAnalytics {
655
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
656
+
657
+ async analyzeTagPerformance(tagName: string, period: number = 30): Promise<TagAnalytics> {
658
+ const taggedUsers = await this.zalo.tag.getUsersByTag(
659
+ this.accessToken,
660
+ tagName,
661
+ { limit: 1000 }
662
+ );
663
+
664
+ const analytics = {
665
+ totalUsers: taggedUsers.total,
666
+ activeUsers: 0,
667
+ totalInteractions: 0,
668
+ totalRevenue: 0,
669
+ avgEngagement: 0,
670
+ conversionRate: 0,
671
+ retentionRate: 0
672
+ };
673
+
674
+ const cutoffTime = Date.now() - (period * 24 * 60 * 60 * 1000);
675
+ let totalEngagement = 0;
676
+ let conversions = 0;
677
+
678
+ for (const user of taggedUsers.data) {
679
+ const userAnalytics = await this.zalo.userManagement.getUserAnalytics(
680
+ this.accessToken,
681
+ user.user_id
682
+ );
683
+
684
+ const interactions = await this.zalo.userManagement.getUserInteractions(
685
+ this.accessToken,
686
+ user.user_id,
687
+ { from_time: cutoffTime, limit: 1000 }
688
+ );
689
+
690
+ if (interactions.length > 0) {
691
+ analytics.activeUsers++;
692
+ analytics.totalInteractions += interactions.length;
693
+
694
+ const engagement = this.calculateEngagementScore(interactions);
695
+ totalEngagement += engagement;
696
+
697
+ // Check for conversions (purchases) in the period
698
+ const purchases = interactions.filter(i => i.type === 'purchase');
699
+ if (purchases.length > 0) {
700
+ conversions++;
701
+ const revenue = purchases.reduce((sum, p) => sum + (p.value || 0), 0);
702
+ analytics.totalRevenue += revenue;
703
+ }
704
+ }
705
+ }
706
+
707
+ analytics.avgEngagement = analytics.activeUsers > 0
708
+ ? totalEngagement / analytics.activeUsers
709
+ : 0;
710
+
711
+ analytics.conversionRate = analytics.activeUsers > 0
712
+ ? conversions / analytics.activeUsers
713
+ : 0;
714
+
715
+ analytics.retentionRate = analytics.totalUsers > 0
716
+ ? analytics.activeUsers / analytics.totalUsers
717
+ : 0;
718
+
719
+ return analytics;
720
+ }
721
+
722
+ async compareTagPerformance(tags: string[]): Promise<TagComparisonReport> {
723
+ const comparisons = [];
724
+
725
+ for (const tag of tags) {
726
+ const performance = await this.analyzeTagPerformance(tag);
727
+ comparisons.push({
728
+ tag,
729
+ ...performance
730
+ });
731
+ }
732
+
733
+ // Sort by total revenue descending
734
+ comparisons.sort((a, b) => b.totalRevenue - a.totalRevenue);
735
+
736
+ return {
737
+ period: '30 days',
738
+ comparisons,
739
+ insights: this.generateComparisonInsights(comparisons)
740
+ };
741
+ }
742
+
743
+ private generateComparisonInsights(comparisons: any[]): string[] {
744
+ const insights = [];
745
+
746
+ // Find best performing tag by revenue
747
+ if (comparisons.length > 0) {
748
+ const topTag = comparisons[0];
749
+ insights.push(`${topTag.tag} is the highest revenue generating segment with ${this.formatCurrency(topTag.totalRevenue)}`);
750
+ }
751
+
752
+ // Find most engaged segment
753
+ const mostEngaged = comparisons.reduce((prev, current) =>
754
+ current.avgEngagement > prev.avgEngagement ? current : prev
755
+ );
756
+ insights.push(`${mostEngaged.tag} has the highest engagement with ${mostEngaged.avgEngagement.toFixed(2)} score`);
757
+
758
+ // Find best conversion rate
759
+ const bestConversion = comparisons.reduce((prev, current) =>
760
+ current.conversionRate > prev.conversionRate ? current : prev
761
+ );
762
+ insights.push(`${bestConversion.tag} has the best conversion rate at ${(bestConversion.conversionRate * 100).toFixed(2)}%`);
763
+
764
+ return insights;
765
+ }
766
+
767
+ async generateTagInsights(): Promise<TagInsights> {
768
+ const tagList = await this.zalo.tag.getTagList(this.accessToken);
769
+
770
+ const insights = {
771
+ totalTags: tagList.total,
772
+ mostPopularTags: [],
773
+ underutilizedTags: [],
774
+ orphanedTags: [],
775
+ recommendedCleanup: []
776
+ };
777
+
778
+ // Find most popular tags
779
+ const sortedByUsers = tagList.data.sort((a, b) => b.user_count - a.user_count);
780
+ insights.mostPopularTags = sortedByUsers.slice(0, 10);
781
+
782
+ // Find underutilized tags
783
+ insights.underutilizedTags = tagList.data.filter(tag =>
784
+ tag.user_count > 0 && tag.user_count < 5
785
+ );
786
+
787
+ // Find orphaned tags (no users)
788
+ insights.orphanedTags = tagList.data.filter(tag => tag.user_count === 0);
789
+
790
+ // Recommend cleanup
791
+ insights.recommendedCleanup = [
792
+ ...insights.orphanedTags.map(tag => ({
793
+ tag: tag.tag_name,
794
+ reason: 'No users tagged',
795
+ action: 'Delete'
796
+ })),
797
+ ...insights.underutilizedTags
798
+ .filter(tag => tag.tag_name.includes('CAMPAIGN_'))
799
+ .map(tag => ({
800
+ tag: tag.tag_name,
801
+ reason: 'Old campaign with few users',
802
+ action: 'Archive or delete'
803
+ }))
804
+ ];
805
+
806
+ return insights;
807
+ }
808
+ }
809
+ ```
810
+
811
+ ### 2. Tag-based Campaign Analysis
812
+
813
+ ```typescript
814
+ class TagCampaignAnalytics {
815
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
816
+
817
+ async analyzeCampaignByTags(
818
+ campaignId: string,
819
+ targetTags: string[]
820
+ ): Promise<CampaignTagAnalytics> {
821
+ const results = new Map<string, TagCampaignMetrics>();
822
+
823
+ for (const tag of targetTags) {
824
+ const taggedUsers = await this.zalo.tag.getUsersByTag(
825
+ this.accessToken,
826
+ tag,
827
+ { limit: 1000 }
828
+ );
829
+
830
+ const campaignMetrics = await this.analyzeCampaignForSegment(
831
+ campaignId,
832
+ taggedUsers.data
833
+ );
834
+
835
+ results.set(tag, {
836
+ segmentSize: taggedUsers.total,
837
+ ...campaignMetrics
838
+ });
839
+ }
840
+
841
+ return {
842
+ campaignId,
843
+ tagPerformance: Array.from(results.entries()),
844
+ overallInsights: this.generateCampaignInsights(results)
845
+ };
846
+ }
847
+
848
+ private async analyzeCampaignForSegment(
849
+ campaignId: string,
850
+ users: UserProfile[]
851
+ ): Promise<CampaignMetrics> {
852
+ let messagesSent = 0;
853
+ let messagesRead = 0;
854
+ let responses = 0;
855
+ let conversions = 0;
856
+ let totalRevenue = 0;
857
+
858
+ for (const user of users) {
859
+ const campaignInteractions = await this.getCampaignInteractionsForUser(
860
+ campaignId,
861
+ user.user_id
862
+ );
863
+
864
+ if (campaignInteractions.length > 0) {
865
+ messagesSent++;
866
+
867
+ // Check if messages were read
868
+ const readMessages = campaignInteractions.filter(i => i.read);
869
+ if (readMessages.length > 0) {
870
+ messagesRead++;
871
+ }
872
+
873
+ // Check for responses
874
+ const userResponses = campaignInteractions.filter(i => i.from_user);
875
+ if (userResponses.length > 0) {
876
+ responses++;
877
+ }
878
+
879
+ // Check for conversions
880
+ const purchases = campaignInteractions.filter(i => i.type === 'purchase');
881
+ if (purchases.length > 0) {
882
+ conversions++;
883
+ totalRevenue += purchases.reduce((sum, p) => sum + (p.value || 0), 0);
884
+ }
885
+ }
886
+ }
887
+
888
+ return {
889
+ messagesSent,
890
+ deliveryRate: messagesSent / users.length,
891
+ readRate: messagesRead / messagesSent,
892
+ responseRate: responses / messagesSent,
893
+ conversionRate: conversions / messagesSent,
894
+ totalRevenue,
895
+ revenuePerUser: totalRevenue / users.length
896
+ };
897
+ }
898
+
899
+ async optimizeTagsForCampaign(
900
+ historicalCampaignData: CampaignHistory[]
901
+ ): Promise<TagOptimizationRecommendations> {
902
+ const tagPerformanceMap = new Map<string, TagPerformanceHistory>();
903
+
904
+ // Analyze historical performance
905
+ for (const campaign of historicalCampaignData) {
906
+ const tagAnalytics = await this.analyzeCampaignByTags(
907
+ campaign.id,
908
+ campaign.targetTags
909
+ );
910
+
911
+ for (const [tag, metrics] of tagAnalytics.tagPerformance) {
912
+ if (!tagPerformanceMap.has(tag)) {
913
+ tagPerformanceMap.set(tag, {
914
+ campaigns: [],
915
+ avgConversionRate: 0,
916
+ avgRevenuePerUser: 0,
917
+ consistency: 0
918
+ });
919
+ }
920
+
921
+ const tagHistory = tagPerformanceMap.get(tag)!;
922
+ tagHistory.campaigns.push(metrics);
923
+ }
924
+ }
925
+
926
+ // Calculate averages and consistency
927
+ for (const [tag, history] of tagPerformanceMap) {
928
+ const campaigns = history.campaigns;
929
+ history.avgConversionRate = campaigns.reduce((sum, c) => sum + c.conversionRate, 0) / campaigns.length;
930
+ history.avgRevenuePerUser = campaigns.reduce((sum, c) => sum + c.revenuePerUser, 0) / campaigns.length;
931
+ history.consistency = 1 - this.calculateVariance(campaigns.map(c => c.conversionRate));
932
+ }
933
+
934
+ // Generate recommendations
935
+ const sortedTags = Array.from(tagPerformanceMap.entries())
936
+ .sort(([,a], [,b]) => b.avgRevenuePerUser - a.avgRevenuePerUser);
937
+
938
+ return {
939
+ topPerformingTags: sortedTags.slice(0, 5).map(([tag, perf]) => ({
940
+ tag,
941
+ avgConversionRate: perf.avgConversionRate,
942
+ avgRevenuePerUser: perf.avgRevenuePerUser,
943
+ recommendation: 'Prioritize for high-value campaigns'
944
+ })),
945
+ consistentTags: sortedTags
946
+ .filter(([,perf]) => perf.consistency > 0.8)
947
+ .slice(0, 5)
948
+ .map(([tag, perf]) => ({
949
+ tag,
950
+ consistency: perf.consistency,
951
+ recommendation: 'Reliable for predictable results'
952
+ })),
953
+ underperformingTags: sortedTags
954
+ .slice(-5)
955
+ .map(([tag, perf]) => ({
956
+ tag,
957
+ avgConversionRate: perf.avgConversionRate,
958
+ recommendation: 'Consider re-segmenting or exclusion'
959
+ }))
960
+ };
961
+ }
962
+ }
963
+ ```
964
+
965
+ ---
966
+
967
+ ## Automated Tag Management
968
+
969
+ ### 1. Auto-tagging Rules Engine
970
+
971
+ ```typescript
972
+ class AutoTaggingEngine {
973
+ private rules: TaggingRule[] = [];
974
+
975
+ constructor(private zalo: ZaloSDK, private accessToken: string) {
976
+ this.initializeDefaultRules();
977
+ }
978
+
979
+ addRule(rule: TaggingRule): void {
980
+ this.rules.push({
981
+ ...rule,
982
+ id: this.generateRuleId(),
983
+ createdAt: Date.now()
984
+ });
985
+ }
986
+
987
+ async processUser(userId: string): Promise<TaggingResult> {
988
+ const userProfile = await this.zalo.userManagement.getUserProfile(
989
+ this.accessToken,
990
+ userId
991
+ );
992
+
993
+ const userAnalytics = await this.zalo.userManagement.getUserAnalytics(
994
+ this.accessToken,
995
+ userId
996
+ );
997
+
998
+ const appliedRules = [];
999
+ const tagsAdded = [];
1000
+ const tagsRemoved = [];
1001
+
1002
+ for (const rule of this.rules) {
1003
+ if (await this.evaluateRule(rule, userProfile, userAnalytics)) {
1004
+ const result = await this.applyRule(rule, userId);
1005
+
1006
+ appliedRules.push(rule.id);
1007
+ tagsAdded.push(...result.tagsAdded);
1008
+ tagsRemoved.push(...result.tagsRemoved);
1009
+ }
1010
+ }
1011
+
1012
+ return {
1013
+ userId,
1014
+ appliedRules,
1015
+ tagsAdded,
1016
+ tagsRemoved,
1017
+ timestamp: Date.now()
1018
+ };
1019
+ }
1020
+
1021
+ private async evaluateRule(
1022
+ rule: TaggingRule,
1023
+ userProfile: UserProfile,
1024
+ userAnalytics: any
1025
+ ): Promise<boolean> {
1026
+ for (const condition of rule.conditions) {
1027
+ const result = await this.evaluateCondition(condition, userProfile, userAnalytics);
1028
+
1029
+ if (rule.operator === 'AND' && !result) {
1030
+ return false;
1031
+ } else if (rule.operator === 'OR' && result) {
1032
+ return true;
1033
+ }
1034
+ }
1035
+
1036
+ return rule.operator === 'AND';
1037
+ }
1038
+
1039
+ private async evaluateCondition(
1040
+ condition: TagCondition,
1041
+ userProfile: UserProfile,
1042
+ userAnalytics: any
1043
+ ): Promise<boolean> {
1044
+ switch (condition.type) {
1045
+ case 'total_spent':
1046
+ return this.compareValues(
1047
+ userAnalytics.total_spent || 0,
1048
+ condition.operator,
1049
+ condition.value
1050
+ );
1051
+
1052
+ case 'days_since_last_purchase':
1053
+ return this.compareValues(
1054
+ userAnalytics.days_since_last_purchase || 999,
1055
+ condition.operator,
1056
+ condition.value
1057
+ );
1058
+
1059
+ case 'engagement_score':
1060
+ return this.compareValues(
1061
+ userAnalytics.engagement_score || 0,
1062
+ condition.operator,
1063
+ condition.value
1064
+ );
1065
+
1066
+ case 'has_tag':
1067
+ const userTags = await this.zalo.tag.getUserTags(this.accessToken, userProfile.user_id);
1068
+ const tagNames = userTags.map(tag => tag.tag_name);
1069
+ return tagNames.includes(condition.value);
1070
+
1071
+ case 'purchase_category':
1072
+ const topCategory = userAnalytics.top_purchase_category;
1073
+ return topCategory === condition.value;
1074
+
1075
+ case 'interaction_frequency':
1076
+ return this.compareValues(
1077
+ userAnalytics.monthly_interactions || 0,
1078
+ condition.operator,
1079
+ condition.value
1080
+ );
1081
+
1082
+ default:
1083
+ return false;
1084
+ }
1085
+ }
1086
+
1087
+ private async applyRule(rule: TaggingRule, userId: string): Promise<RuleApplication> {
1088
+ const tagsAdded = [];
1089
+ const tagsRemoved = [];
1090
+
1091
+ // Add tags
1092
+ if (rule.actions.addTags && rule.actions.addTags.length > 0) {
1093
+ await this.zalo.tag.tagUser(this.accessToken, userId, rule.actions.addTags);
1094
+ tagsAdded.push(...rule.actions.addTags);
1095
+ }
1096
+
1097
+ // Remove tags
1098
+ if (rule.actions.removeTags && rule.actions.removeTags.length > 0) {
1099
+ await this.zalo.tag.untagUser(this.accessToken, userId, rule.actions.removeTags);
1100
+ tagsRemoved.push(...rule.actions.removeTags);
1101
+ }
1102
+
1103
+ return { tagsAdded, tagsRemoved };
1104
+ }
1105
+
1106
+ private initializeDefaultRules(): void {
1107
+ // VIP Customer Rule
1108
+ this.addRule({
1109
+ name: 'VIP Customer Auto-Tag',
1110
+ conditions: [
1111
+ { type: 'total_spent', operator: 'gte', value: 50000000 },
1112
+ { type: 'engagement_score', operator: 'gte', value: 70 }
1113
+ ],
1114
+ operator: 'AND',
1115
+ actions: {
1116
+ addTags: ['VIP_CUSTOMER', 'HIGH_VALUE'],
1117
+ removeTags: ['STANDARD_CUSTOMER']
1118
+ },
1119
+ active: true
1120
+ });
1121
+
1122
+ // Churn Risk Rule
1123
+ this.addRule({
1124
+ name: 'Churn Risk Detection',
1125
+ conditions: [
1126
+ { type: 'days_since_last_purchase', operator: 'gte', value: 90 },
1127
+ { type: 'engagement_score', operator: 'lte', value: 30 }
1128
+ ],
1129
+ operator: 'AND',
1130
+ actions: {
1131
+ addTags: ['CHURN_RISK', 'RE_ENGAGEMENT_NEEDED'],
1132
+ removeTags: ['ACTIVE_CUSTOMER']
1133
+ },
1134
+ active: true
1135
+ });
1136
+
1137
+ // Frequent Buyer Rule
1138
+ this.addRule({
1139
+ name: 'Frequent Buyer',
1140
+ conditions: [
1141
+ { type: 'interaction_frequency', operator: 'gte', value: 15 },
1142
+ { type: 'days_since_last_purchase', operator: 'lte', value: 30 }
1143
+ ],
1144
+ operator: 'AND',
1145
+ actions: {
1146
+ addTags: ['FREQUENT_BUYER', 'LOYALTY_PROGRAM_ELIGIBLE']
1147
+ },
1148
+ active: true
1149
+ });
1150
+ }
1151
+
1152
+ // Batch process all users
1153
+ async processBatch(userIds?: string[]): Promise<BatchTaggingResult> {
1154
+ const usersToProcess = userIds || await this.getAllActiveUserIds();
1155
+ const results = [];
1156
+ const errors = [];
1157
+
1158
+ console.log(`Processing ${usersToProcess.length} users with ${this.rules.length} rules`);
1159
+
1160
+ for (const userId of usersToProcess) {
1161
+ try {
1162
+ const result = await this.processUser(userId);
1163
+ results.push(result);
1164
+ } catch (error) {
1165
+ errors.push({ userId, error: error.message });
1166
+ }
1167
+ }
1168
+
1169
+ return {
1170
+ totalProcessed: usersToProcess.length,
1171
+ successful: results.length,
1172
+ failed: errors.length,
1173
+ results,
1174
+ errors
1175
+ };
1176
+ }
1177
+ }
1178
+ ```
1179
+
1180
+ ### 2. Scheduled Tag Maintenance
1181
+
1182
+ ```typescript
1183
+ class TagMaintenanceScheduler {
1184
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
1185
+
1186
+ async runDailyMaintenance(): Promise<MaintenanceReport> {
1187
+ console.log('Starting daily tag maintenance...');
1188
+
1189
+ const tasks = [
1190
+ this.cleanupOrphanedTags(),
1191
+ this.archiveExpiredCampaignTags(),
1192
+ this.updateLifecycleTags(),
1193
+ this.refreshEngagementTags(),
1194
+ this.validateTagConsistency()
1195
+ ];
1196
+
1197
+ const results = await Promise.allSettled(tasks);
1198
+
1199
+ return {
1200
+ timestamp: Date.now(),
1201
+ tasks: results.map((result, index) => ({
1202
+ task: ['cleanup', 'archive', 'lifecycle', 'engagement', 'validate'][index],
1203
+ status: result.status,
1204
+ result: result.status === 'fulfilled' ? result.value : result.reason
1205
+ }))
1206
+ };
1207
+ }
1208
+
1209
+ private async cleanupOrphanedTags(): Promise<CleanupResult> {
1210
+ const tagList = await this.zalo.tag.getTagList(this.accessToken);
1211
+ const orphanedTags = tagList.data.filter(tag => tag.user_count === 0);
1212
+
1213
+ let deleted = 0;
1214
+ for (const tag of orphanedTags) {
1215
+ try {
1216
+ await this.zalo.tag.deleteTag(this.accessToken, tag.tag_name);
1217
+ deleted++;
1218
+ } catch (error) {
1219
+ console.error(`Failed to delete orphaned tag ${tag.tag_name}:`, error);
1220
+ }
1221
+ }
1222
+
1223
+ return { deleted, total: orphanedTags.length };
1224
+ }
1225
+
1226
+ private async archiveExpiredCampaignTags(): Promise<ArchiveResult> {
1227
+ const tagList = await this.zalo.tag.getTagList(this.accessToken);
1228
+ const campaignTags = tagList.data.filter(tag =>
1229
+ tag.tag_name.startsWith('CAMPAIGN_') &&
1230
+ this.isCampaignExpired(tag.tag_name)
1231
+ );
1232
+
1233
+ let archived = 0;
1234
+ for (const tag of campaignTags) {
1235
+ try {
1236
+ const archivedTagName = `ARCHIVED_${tag.tag_name}`;
1237
+ await this.zalo.tag.createTag(this.accessToken, archivedTagName);
1238
+
1239
+ // Move users to archived tag
1240
+ const users = await this.zalo.tag.getUsersByTag(this.accessToken, tag.tag_name);
1241
+ for (const user of users.data) {
1242
+ await this.zalo.tag.tagUser(this.accessToken, user.user_id, [archivedTagName]);
1243
+ await this.zalo.tag.untagUser(this.accessToken, user.user_id, [tag.tag_name]);
1244
+ }
1245
+
1246
+ await this.zalo.tag.deleteTag(this.accessToken, tag.tag_name);
1247
+ archived++;
1248
+ } catch (error) {
1249
+ console.error(`Failed to archive campaign tag ${tag.tag_name}:`, error);
1250
+ }
1251
+ }
1252
+
1253
+ return { archived, total: campaignTags.length };
1254
+ }
1255
+
1256
+ private async updateLifecycleTags(): Promise<UpdateResult> {
1257
+ const lifecycleTagging = new CustomerLifecycleTagging(this.zalo, this.accessToken);
1258
+ const allUsers = await this.getAllActiveUsers();
1259
+
1260
+ let updated = 0;
1261
+ for (const user of allUsers) {
1262
+ try {
1263
+ const currentStage = await this.determineLifecycleStage(user.user_id);
1264
+ await lifecycleTagging.tagUserByLifecycleStage(user.user_id, currentStage);
1265
+ updated++;
1266
+ } catch (error) {
1267
+ console.error(`Failed to update lifecycle tags for user ${user.user_id}:`, error);
1268
+ }
1269
+ }
1270
+
1271
+ return { updated, total: allUsers.length };
1272
+ }
1273
+
1274
+ private isCampaignExpired(tagName: string): boolean {
1275
+ // Extract date from campaign tag name
1276
+ const dateMatch = tagName.match(/(\d{4})_(\d{1,2})/);
1277
+ if (!dateMatch) return false;
1278
+
1279
+ const [, year, month] = dateMatch;
1280
+ const campaignDate = new Date(parseInt(year), parseInt(month) - 1);
1281
+ const threeMonthsAgo = new Date();
1282
+ threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
1283
+
1284
+ return campaignDate < threeMonthsAgo;
1285
+ }
1286
+ }
1287
+ ```
1288
+
1289
+ ---
1290
+
1291
+ ## Best Practices
1292
+
1293
+ ### 1. Tag Naming Conventions
1294
+
1295
+ ```typescript
1296
+ // ✅ Good tag naming patterns
1297
+ const tagNamingPatterns = {
1298
+ // Category_SubCategory_Qualifier
1299
+ customer_segments: [
1300
+ 'CUSTOMER_VIP_DIAMOND',
1301
+ 'CUSTOMER_VIP_GOLD',
1302
+ 'CUSTOMER_REGULAR_ACTIVE',
1303
+ 'CUSTOMER_REGULAR_INACTIVE'
1304
+ ],
1305
+
1306
+ // Action_Target_Timeframe
1307
+ campaigns: [
1308
+ 'CAMPAIGN_BLACKFRIDAY_2024',
1309
+ 'CAMPAIGN_NEWPRODUCT_Q1',
1310
+ 'CAMPAIGN_RETENTION_MONTHLY'
1311
+ ],
1312
+
1313
+ // Behavior_Level_Context
1314
+ engagement: [
1315
+ 'ENGAGEMENT_HIGH_RECENT',
1316
+ 'ENGAGEMENT_LOW_ATRISK',
1317
+ 'ENGAGEMENT_NONE_CHURNED'
1318
+ ],
1319
+
1320
+ // Lifecycle_Stage_Status
1321
+ lifecycle: [
1322
+ 'LIFECYCLE_PROSPECT_NEW',
1323
+ 'LIFECYCLE_CUSTOMER_ACTIVE',
1324
+ 'LIFECYCLE_CUSTOMER_REPEAT'
1325
+ ]
1326
+ };
1327
+
1328
+ // ❌ Avoid these patterns
1329
+ const badTagNames = [
1330
+ 'tag1', 'customers', 'important', 'temp', 'test'
1331
+ ];
1332
+ ```
1333
+
1334
+ ### 2. Tag Architecture Best Practices
1335
+
1336
+ ```typescript
1337
+ class TagArchitectureBestPractices {
1338
+ // Use hierarchical tagging
1339
+ static getTagHierarchy(): TagHierarchy {
1340
+ return {
1341
+ 'CUSTOMER': {
1342
+ 'CUSTOMER_VALUE': ['HIGH', 'MEDIUM', 'LOW'],
1343
+ 'CUSTOMER_STATUS': ['ACTIVE', 'INACTIVE', 'CHURNED'],
1344
+ 'CUSTOMER_TYPE': ['B2B', 'B2C', 'ENTERPRISE']
1345
+ },
1346
+ 'ENGAGEMENT': {
1347
+ 'ENGAGEMENT_LEVEL': ['VERY_HIGH', 'HIGH', 'MEDIUM', 'LOW'],
1348
+ 'ENGAGEMENT_CHANNEL': ['ZALO', 'EMAIL', 'SMS', 'PHONE']
1349
+ },
1350
+ 'LIFECYCLE': {
1351
+ 'LIFECYCLE_STAGE': ['AWARENESS', 'CONSIDERATION', 'PURCHASE', 'RETENTION', 'ADVOCACY']
1352
+ }
1353
+ };
1354
+ }
1355
+
1356
+ // Implement mutual exclusivity where appropriate
1357
+ static getMutuallyExclusiveTags(): MutuallyExclusiveGroups {
1358
+ return [
1359
+ ['CUSTOMER_VALUE_HIGH', 'CUSTOMER_VALUE_MEDIUM', 'CUSTOMER_VALUE_LOW'],
1360
+ ['LIFECYCLE_AWARENESS', 'LIFECYCLE_CONSIDERATION', 'LIFECYCLE_PURCHASE', 'LIFECYCLE_RETENTION'],
1361
+ ['ENGAGEMENT_VERY_HIGH', 'ENGAGEMENT_HIGH', 'ENGAGEMENT_MEDIUM', 'ENGAGEMENT_LOW']
1362
+ ];
1363
+ }
1364
+
1365
+ // Tag governance rules
1366
+ static getTagGovernanceRules(): TagGovernanceRules {
1367
+ return {
1368
+ maxTagsPerUser: 25,
1369
+ requireApprovalForTags: ['VIP_*', 'ENTERPRISE_*'],
1370
+ autoExpirationTags: ['CAMPAIGN_*', 'PROMOTION_*'],
1371
+ protectedTags: ['SYSTEM_*', 'ADMIN_*'],
1372
+ requiredTags: ['LIFECYCLE_*', 'CUSTOMER_VALUE_*']
1373
+ };
1374
+ }
1375
+ }
1376
+ ```
1377
+
1378
+ ---
1379
+
1380
+ ## Testing Tag Management
1381
+
1382
+ ### 1. Unit Tests
1383
+
1384
+ ```typescript
1385
+ // tag-management.test.ts
1386
+ import { ZaloSDK } from '@warriorteam/redai-zalo-sdk';
1387
+
1388
+ describe('Tag Management', () => {
1389
+ const zalo = new ZaloSDK({
1390
+ appId: 'test_app_id',
1391
+ appSecret: 'test_app_secret'
1392
+ });
1393
+
1394
+ it('should create and delete tags', async () => {
1395
+ const mockTagResponse = {
1396
+ error: 0,
1397
+ message: 'Success',
1398
+ data: { tag_id: 'test_tag_id', tag_name: 'TEST_TAG' }
1399
+ };
1400
+
1401
+ jest.spyOn(zalo.tag, 'createTag').mockResolvedValue(mockTagResponse);
1402
+ jest.spyOn(zalo.tag, 'deleteTag').mockResolvedValue({ error: 0 });
1403
+
1404
+ // Test tag creation
1405
+ const result = await zalo.tag.createTag('test_token', 'TEST_TAG');
1406
+ expect(result.tag_name).toBe('TEST_TAG');
1407
+
1408
+ // Test tag deletion
1409
+ await expect(zalo.tag.deleteTag('test_token', 'TEST_TAG')).resolves.not.toThrow();
1410
+ });
1411
+
1412
+ it('should tag and untag users', async () => {
1413
+ jest.spyOn(zalo.tag, 'tagUser').mockResolvedValue({ error: 0 });
1414
+ jest.spyOn(zalo.tag, 'untagUser').mockResolvedValue({ error: 0 });
1415
+
1416
+ await expect(zalo.tag.tagUser('test_token', 'user123', ['TAG1', 'TAG2'])).resolves.not.toThrow();
1417
+ await expect(zalo.tag.untagUser('test_token', 'user123', ['TAG1'])).resolves.not.toThrow();
1418
+ });
1419
+ });
1420
+ ```
1421
+
1422
+ ---
1423
+
1424
+ ## Troubleshooting
1425
+
1426
+ ### Common Issues
1427
+
1428
+ **Q: "Tag already exists" error**
1429
+ ```
1430
+ A: Kiểm tra danh sách tags trước khi tạo mới.
1431
+ Sử dụng try-catch để handle lỗi này gracefully.
1432
+ ```
1433
+
1434
+ **Q: User không được tag thành công**
1435
+ ```
1436
+ A: Kiểm tra:
1437
+ - User ID có hợp lệ không
1438
+ - User có còn follow OA không
1439
+ - Tag name có đúng format không
1440
+ - Có đủ quyền để tag user không
1441
+ ```
1442
+
1443
+ **Q: Bulk tagging bị rate limited**
1444
+ ```
1445
+ A: Implement batch processing với delays:
1446
+ - Process 5-10 users per batch
1447
+ - Add 1-2 second delays between batches
1448
+ - Implement exponential backoff for retries
1449
+ ```
1450
+
1451
+ ---
1452
+
1453
+ ## Next Steps
1454
+
1455
+ Sau khi nắm vững Tag Management:
1456
+
1457
+ 1. **[Video Upload](./VIDEO_UPLOAD.md)** - Upload và manage media content
1458
+ 2. **[Error Handling](./ERROR_HANDLING.md)** - Comprehensive error handling
1459
+ 3. **[Group Management](./GROUP_MANAGEMENT.md)** - Zalo group operations
1460
+ 4. **[Webhook Events](./WEBHOOK_EVENTS.md)** - Real-time event processing
1461
+
1462
+ Tham khảo **[API Reference](./API_REFERENCE.md)** để biết chi tiết về tất cả tag management methods.