strapi-plugin-magic-mail 2.2.6 → 2.3.1

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.
@@ -5,6 +5,8 @@ const require$$1$1 = require("os");
5
5
  const require$$0$3 = require("mustache");
6
6
  const require$$1$2 = require("html-to-text");
7
7
  const require$$2$2 = require("decode-html");
8
+ const require$$0$4 = require("path");
9
+ const require$$1$3 = require("fs");
8
10
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
9
11
  const require$$0__default$1 = /* @__PURE__ */ _interopDefault(require$$0$2);
10
12
  const require$$0__default = /* @__PURE__ */ _interopDefault(require$$0$1);
@@ -12,6 +14,8 @@ const require$$1__default = /* @__PURE__ */ _interopDefault(require$$1$1);
12
14
  const require$$0__default$2 = /* @__PURE__ */ _interopDefault(require$$0$3);
13
15
  const require$$1__default$1 = /* @__PURE__ */ _interopDefault(require$$1$2);
14
16
  const require$$2__default = /* @__PURE__ */ _interopDefault(require$$2$2);
17
+ const require$$0__default$3 = /* @__PURE__ */ _interopDefault(require$$0$4);
18
+ const require$$1__default$2 = /* @__PURE__ */ _interopDefault(require$$1$3);
15
19
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
16
20
  function getDefaultExportFromCjs(x2) {
17
21
  return x2 && x2.__esModule && Object.prototype.hasOwnProperty.call(x2, "default") ? x2["default"] : x2;
@@ -265,7 +269,7 @@ const collectionName$5 = "magic_mail_routing_rules";
265
269
  const info$5 = { "singularName": "routing-rule", "pluralName": "routing-rules", "displayName": "Email Routing Rule", "description": "Rules for routing emails to specific accounts" };
266
270
  const options$5 = { "draftAndPublish": false };
267
271
  const pluginOptions$5 = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
268
- const attributes$5 = { "name": { "type": "string", "required": true, "unique": true }, "description": { "type": "text" }, "isActive": { "type": "boolean", "default": true, "required": true }, "priority": { "type": "integer", "default": 1, "min": 1 }, "matchType": { "type": "enumeration", "enum": ["emailType", "subject", "recipient", "template", "custom"], "required": true, "default": "emailType" }, "matchValue": { "type": "text", "required": true }, "accountName": { "type": "string", "required": true }, "fallbackAccountName": { "type": "string" } };
272
+ const attributes$5 = { "name": { "type": "string", "required": true, "unique": true }, "description": { "type": "text" }, "isActive": { "type": "boolean", "default": true, "required": true }, "priority": { "type": "integer", "default": 1, "min": 1 }, "matchType": { "type": "enumeration", "enum": ["emailType", "subject", "recipient", "template", "custom"], "required": true, "default": "emailType" }, "matchValue": { "type": "text", "required": true }, "accountName": { "type": "string", "required": true }, "fallbackAccountName": { "type": "string" }, "whatsappFallback": { "type": "boolean", "default": false }, "whatsappPhoneField": { "type": "string" } };
269
273
  const require$$1 = {
270
274
  kind: kind$5,
271
275
  collectionName: collectionName$5,
@@ -402,6 +406,88 @@ function requireController() {
402
406
  strapi.log.error("[magic-mail] Error sending email:", err);
403
407
  ctx.throw(500, err.message || "Failed to send email");
404
408
  }
409
+ },
410
+ /**
411
+ * Send message via Email or WhatsApp (unified API)
412
+ * POST /api/magic-mail/send-message
413
+ * Body: { channel: 'email' | 'whatsapp' | 'auto', to, phoneNumber, subject, message, ... }
414
+ */
415
+ async sendMessage(ctx) {
416
+ try {
417
+ const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
418
+ const result = await emailRouter2.sendMessage(ctx.request.body);
419
+ ctx.body = {
420
+ success: true,
421
+ ...result
422
+ };
423
+ } catch (err) {
424
+ strapi.log.error("[magic-mail] Error sending message:", err);
425
+ ctx.throw(500, err.message || "Failed to send message");
426
+ }
427
+ },
428
+ /**
429
+ * Send WhatsApp message
430
+ * POST /api/magic-mail/send-whatsapp
431
+ * Body: { phoneNumber, message, templateId?, templateData? }
432
+ */
433
+ async sendWhatsApp(ctx) {
434
+ try {
435
+ const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
436
+ const result = await emailRouter2.sendWhatsApp(ctx.request.body);
437
+ ctx.body = {
438
+ success: true,
439
+ ...result
440
+ };
441
+ } catch (err) {
442
+ strapi.log.error("[magic-mail] Error sending WhatsApp:", err);
443
+ ctx.throw(500, err.message || "Failed to send WhatsApp message");
444
+ }
445
+ },
446
+ /**
447
+ * Get WhatsApp connection status
448
+ * GET /api/magic-mail/whatsapp/status
449
+ */
450
+ async getWhatsAppStatus(ctx) {
451
+ try {
452
+ const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
453
+ const status = emailRouter2.getWhatsAppStatus();
454
+ ctx.body = {
455
+ success: true,
456
+ data: status
457
+ };
458
+ } catch (err) {
459
+ strapi.log.error("[magic-mail] Error getting WhatsApp status:", err);
460
+ ctx.body = {
461
+ success: false,
462
+ data: {
463
+ isConnected: false,
464
+ status: "error",
465
+ error: err.message
466
+ }
467
+ };
468
+ }
469
+ },
470
+ /**
471
+ * Check if phone number is on WhatsApp
472
+ * GET /api/magic-mail/whatsapp/check/:phoneNumber
473
+ */
474
+ async checkWhatsAppNumber(ctx) {
475
+ try {
476
+ const { phoneNumber } = ctx.params;
477
+ if (!phoneNumber) {
478
+ ctx.throw(400, "Phone number is required");
479
+ return;
480
+ }
481
+ const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
482
+ const result = await emailRouter2.checkWhatsAppNumber(phoneNumber);
483
+ ctx.body = {
484
+ success: true,
485
+ data: result
486
+ };
487
+ } catch (err) {
488
+ strapi.log.error("[magic-mail] Error checking WhatsApp number:", err);
489
+ ctx.throw(500, err.message || "Failed to check phone number");
490
+ }
405
491
  }
406
492
  };
407
493
  return controller;
@@ -520,7 +606,7 @@ function requireAccounts() {
520
606
  if (!testEmail) {
521
607
  ctx.throw(400, "testEmail is required");
522
608
  }
523
- strapi.log.info("[magic-mail] 🧪 Testing Strapi Email Service integration...");
609
+ strapi.log.info("[magic-mail] [TEST] Testing Strapi Email Service integration...");
524
610
  strapi.log.info('[magic-mail] [EMAIL] Calling strapi.plugin("email").service("email").send()');
525
611
  if (accountName) {
526
612
  strapi.log.info(`[magic-mail] [FORCE] Forcing specific account: ${accountName}`);
@@ -2236,7 +2322,7 @@ function requireTest() {
2236
2322
  async testRelations(ctx) {
2237
2323
  try {
2238
2324
  console.log("\n" + "=".repeat(60));
2239
- console.log("🧪 TEST: Template Version Relations (Document Service API)");
2325
+ console.log("[TEST] Template - Version Relations (Document Service API)");
2240
2326
  console.log("=".repeat(60));
2241
2327
  let test1Success = false;
2242
2328
  let test1ReverseSuccess = false;
@@ -2404,7 +2490,7 @@ function requireTest() {
2404
2490
  } else {
2405
2491
  console.log(` [ERROR] FEHLER: Falsche Anzahl Versionen!`);
2406
2492
  }
2407
- console.log("\n🧹 Cleanup Test 3...");
2493
+ console.log("\n[CLEANUP] Cleanup Test 3...");
2408
2494
  if (afterSecondUpdate.versions) {
2409
2495
  for (const version3 of afterSecondUpdate.versions) {
2410
2496
  await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({ documentId: version3.documentId });
@@ -2422,7 +2508,7 @@ function requireTest() {
2422
2508
  console.log(`
2423
2509
  [INFO] Template: "${finalTemplate.name}" (documentId: ${finalTemplate.documentId})`);
2424
2510
  console.log(` Anzahl Versionen: ${finalTemplate.versions?.length || 0}`);
2425
- console.log("\n🧹 Aufräumen...");
2511
+ console.log("\n[CLEANUP] Aufraumen...");
2426
2512
  await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({ documentId: version1.documentId });
2427
2513
  await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({ documentId: version2.documentId });
2428
2514
  await strapi.documents(EMAIL_TEMPLATE_UID).delete({ documentId: testTemplate.documentId });
@@ -2452,6 +2538,219 @@ function requireTest() {
2452
2538
  };
2453
2539
  return test;
2454
2540
  }
2541
+ var whatsapp$1;
2542
+ var hasRequiredWhatsapp$1;
2543
+ function requireWhatsapp$1() {
2544
+ if (hasRequiredWhatsapp$1) return whatsapp$1;
2545
+ hasRequiredWhatsapp$1 = 1;
2546
+ whatsapp$1 = {
2547
+ /**
2548
+ * Check if WhatsApp/Baileys is available
2549
+ * GET /magic-mail/whatsapp/available
2550
+ */
2551
+ async checkAvailable(ctx) {
2552
+ try {
2553
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2554
+ const available = await whatsappService.isAvailable();
2555
+ ctx.body = {
2556
+ success: true,
2557
+ data: {
2558
+ available,
2559
+ message: available ? "WhatsApp integration is available" : "Baileys not installed. Run: npm install @whiskeysockets/baileys pino qrcode"
2560
+ }
2561
+ };
2562
+ } catch (error) {
2563
+ ctx.throw(500, error.message);
2564
+ }
2565
+ },
2566
+ /**
2567
+ * Get WhatsApp connection status
2568
+ * GET /magic-mail/whatsapp/status
2569
+ */
2570
+ async getStatus(ctx) {
2571
+ try {
2572
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2573
+ const status = whatsappService.getStatus();
2574
+ const sessionInfo = await whatsappService.getSessionInfo();
2575
+ ctx.body = {
2576
+ success: true,
2577
+ data: {
2578
+ ...status,
2579
+ session: sessionInfo
2580
+ }
2581
+ };
2582
+ } catch (error) {
2583
+ ctx.throw(500, error.message);
2584
+ }
2585
+ },
2586
+ /**
2587
+ * Connect to WhatsApp (generates QR code if needed)
2588
+ * POST /magic-mail/whatsapp/connect
2589
+ */
2590
+ async connect(ctx) {
2591
+ try {
2592
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2593
+ const result = await whatsappService.connect();
2594
+ ctx.body = {
2595
+ success: result.success,
2596
+ data: result
2597
+ };
2598
+ } catch (error) {
2599
+ ctx.throw(500, error.message);
2600
+ }
2601
+ },
2602
+ /**
2603
+ * Disconnect from WhatsApp
2604
+ * POST /magic-mail/whatsapp/disconnect
2605
+ */
2606
+ async disconnect(ctx) {
2607
+ try {
2608
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2609
+ const result = await whatsappService.disconnect();
2610
+ ctx.body = {
2611
+ success: result.success,
2612
+ data: result
2613
+ };
2614
+ } catch (error) {
2615
+ ctx.throw(500, error.message);
2616
+ }
2617
+ },
2618
+ /**
2619
+ * Send a test message
2620
+ * POST /magic-mail/whatsapp/send-test
2621
+ */
2622
+ async sendTest(ctx) {
2623
+ try {
2624
+ const { phoneNumber, message } = ctx.request.body;
2625
+ if (!phoneNumber) {
2626
+ return ctx.badRequest("Phone number is required");
2627
+ }
2628
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2629
+ const testMessage = message || `[MagicMail Test] This is a test message sent at ${(/* @__PURE__ */ new Date()).toLocaleString()}`;
2630
+ const result = await whatsappService.sendMessage(phoneNumber, testMessage);
2631
+ ctx.body = {
2632
+ success: result.success,
2633
+ data: result
2634
+ };
2635
+ } catch (error) {
2636
+ ctx.throw(500, error.message);
2637
+ }
2638
+ },
2639
+ /**
2640
+ * Send a message using a template
2641
+ * POST /magic-mail/whatsapp/send-template
2642
+ */
2643
+ async sendTemplateMessage(ctx) {
2644
+ try {
2645
+ const { phoneNumber, templateName, variables } = ctx.request.body;
2646
+ if (!phoneNumber || !templateName) {
2647
+ return ctx.badRequest("Phone number and template name are required");
2648
+ }
2649
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2650
+ const result = await whatsappService.sendTemplateMessage(phoneNumber, templateName, variables || {});
2651
+ ctx.body = {
2652
+ success: result.success,
2653
+ data: result
2654
+ };
2655
+ } catch (error) {
2656
+ ctx.throw(500, error.message);
2657
+ }
2658
+ },
2659
+ /**
2660
+ * Check if a phone number is on WhatsApp
2661
+ * POST /magic-mail/whatsapp/check-number
2662
+ */
2663
+ async checkNumber(ctx) {
2664
+ try {
2665
+ const { phoneNumber } = ctx.request.body;
2666
+ if (!phoneNumber) {
2667
+ return ctx.badRequest("Phone number is required");
2668
+ }
2669
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2670
+ const result = await whatsappService.checkNumber(phoneNumber);
2671
+ ctx.body = {
2672
+ success: result.success,
2673
+ data: result
2674
+ };
2675
+ } catch (error) {
2676
+ ctx.throw(500, error.message);
2677
+ }
2678
+ },
2679
+ /**
2680
+ * Get all WhatsApp templates
2681
+ * GET /magic-mail/whatsapp/templates
2682
+ */
2683
+ async getTemplates(ctx) {
2684
+ try {
2685
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2686
+ const templates = await whatsappService.getTemplates();
2687
+ ctx.body = {
2688
+ success: true,
2689
+ data: templates
2690
+ };
2691
+ } catch (error) {
2692
+ ctx.throw(500, error.message);
2693
+ }
2694
+ },
2695
+ /**
2696
+ * Save a WhatsApp template
2697
+ * POST /magic-mail/whatsapp/templates
2698
+ */
2699
+ async saveTemplate(ctx) {
2700
+ try {
2701
+ const { templateName, templateContent } = ctx.request.body;
2702
+ if (!templateName || !templateContent) {
2703
+ return ctx.badRequest("Template name and content are required");
2704
+ }
2705
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2706
+ const result = await whatsappService.saveTemplate(templateName, templateContent);
2707
+ ctx.body = {
2708
+ success: result.success,
2709
+ data: result
2710
+ };
2711
+ } catch (error) {
2712
+ ctx.throw(500, error.message);
2713
+ }
2714
+ },
2715
+ /**
2716
+ * Delete a WhatsApp template
2717
+ * DELETE /magic-mail/whatsapp/templates/:templateName
2718
+ */
2719
+ async deleteTemplate(ctx) {
2720
+ try {
2721
+ const { templateName } = ctx.params;
2722
+ if (!templateName) {
2723
+ return ctx.badRequest("Template name is required");
2724
+ }
2725
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2726
+ const result = await whatsappService.deleteTemplate(templateName);
2727
+ ctx.body = {
2728
+ success: result.success,
2729
+ data: result
2730
+ };
2731
+ } catch (error) {
2732
+ ctx.throw(500, error.message);
2733
+ }
2734
+ },
2735
+ /**
2736
+ * Get session info
2737
+ * GET /magic-mail/whatsapp/session
2738
+ */
2739
+ async getSession(ctx) {
2740
+ try {
2741
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2742
+ const sessionInfo = await whatsappService.getSessionInfo();
2743
+ ctx.body = {
2744
+ success: true,
2745
+ data: sessionInfo
2746
+ };
2747
+ } catch (error) {
2748
+ ctx.throw(500, error.message);
2749
+ }
2750
+ }
2751
+ };
2752
+ return whatsapp$1;
2753
+ }
2455
2754
  var controllers;
2456
2755
  var hasRequiredControllers;
2457
2756
  function requireControllers() {
@@ -2465,6 +2764,7 @@ function requireControllers() {
2465
2764
  const emailDesigner2 = requireEmailDesigner$1();
2466
2765
  const analytics2 = requireAnalytics$1();
2467
2766
  const test2 = requireTest();
2767
+ const whatsapp2 = requireWhatsapp$1();
2468
2768
  controllers = {
2469
2769
  controller: controller2,
2470
2770
  accounts: accounts2,
@@ -2473,7 +2773,8 @@ function requireControllers() {
2473
2773
  license: license2,
2474
2774
  emailDesigner: emailDesigner2,
2475
2775
  analytics: analytics2,
2476
- test: test2
2776
+ test: test2,
2777
+ whatsapp: whatsapp2
2477
2778
  };
2478
2779
  return controllers;
2479
2780
  }
@@ -2948,6 +3249,106 @@ function requireAdmin() {
2948
3249
  policies: ["admin::isAuthenticatedAdmin"],
2949
3250
  description: "Test template-version relations"
2950
3251
  }
3252
+ },
3253
+ // WhatsApp Routes
3254
+ {
3255
+ method: "GET",
3256
+ path: "/whatsapp/available",
3257
+ handler: "whatsapp.checkAvailable",
3258
+ config: {
3259
+ policies: ["admin::isAuthenticatedAdmin"],
3260
+ description: "Check if WhatsApp/Baileys is available"
3261
+ }
3262
+ },
3263
+ {
3264
+ method: "GET",
3265
+ path: "/whatsapp/status",
3266
+ handler: "whatsapp.getStatus",
3267
+ config: {
3268
+ policies: ["admin::isAuthenticatedAdmin"],
3269
+ description: "Get WhatsApp connection status"
3270
+ }
3271
+ },
3272
+ {
3273
+ method: "POST",
3274
+ path: "/whatsapp/connect",
3275
+ handler: "whatsapp.connect",
3276
+ config: {
3277
+ policies: ["admin::isAuthenticatedAdmin"],
3278
+ description: "Connect to WhatsApp (generates QR if needed)"
3279
+ }
3280
+ },
3281
+ {
3282
+ method: "POST",
3283
+ path: "/whatsapp/disconnect",
3284
+ handler: "whatsapp.disconnect",
3285
+ config: {
3286
+ policies: ["admin::isAuthenticatedAdmin"],
3287
+ description: "Disconnect from WhatsApp"
3288
+ }
3289
+ },
3290
+ {
3291
+ method: "POST",
3292
+ path: "/whatsapp/send-test",
3293
+ handler: "whatsapp.sendTest",
3294
+ config: {
3295
+ policies: ["admin::isAuthenticatedAdmin"],
3296
+ description: "Send a test WhatsApp message"
3297
+ }
3298
+ },
3299
+ {
3300
+ method: "POST",
3301
+ path: "/whatsapp/send-template",
3302
+ handler: "whatsapp.sendTemplateMessage",
3303
+ config: {
3304
+ policies: ["admin::isAuthenticatedAdmin"],
3305
+ description: "Send WhatsApp message using template"
3306
+ }
3307
+ },
3308
+ {
3309
+ method: "POST",
3310
+ path: "/whatsapp/check-number",
3311
+ handler: "whatsapp.checkNumber",
3312
+ config: {
3313
+ policies: ["admin::isAuthenticatedAdmin"],
3314
+ description: "Check if phone number is on WhatsApp"
3315
+ }
3316
+ },
3317
+ {
3318
+ method: "GET",
3319
+ path: "/whatsapp/templates",
3320
+ handler: "whatsapp.getTemplates",
3321
+ config: {
3322
+ policies: ["admin::isAuthenticatedAdmin"],
3323
+ description: "Get all WhatsApp message templates"
3324
+ }
3325
+ },
3326
+ {
3327
+ method: "POST",
3328
+ path: "/whatsapp/templates",
3329
+ handler: "whatsapp.saveTemplate",
3330
+ config: {
3331
+ policies: ["admin::isAuthenticatedAdmin"],
3332
+ description: "Save a WhatsApp message template"
3333
+ }
3334
+ },
3335
+ {
3336
+ method: "DELETE",
3337
+ path: "/whatsapp/templates/:templateName",
3338
+ handler: "whatsapp.deleteTemplate",
3339
+ config: {
3340
+ policies: ["admin::isAuthenticatedAdmin"],
3341
+ description: "Delete a WhatsApp message template"
3342
+ }
3343
+ },
3344
+ {
3345
+ method: "GET",
3346
+ path: "/whatsapp/session",
3347
+ handler: "whatsapp.getSession",
3348
+ config: {
3349
+ policies: ["admin::isAuthenticatedAdmin"],
3350
+ description: "Get WhatsApp session info"
3351
+ }
2951
3352
  }
2952
3353
  ]
2953
3354
  };
@@ -2961,6 +3362,7 @@ function requireContentApi() {
2961
3362
  contentApi = {
2962
3363
  type: "content-api",
2963
3364
  routes: [
3365
+ // ============= EMAIL ROUTES =============
2964
3366
  {
2965
3367
  method: "POST",
2966
3368
  path: "/send",
@@ -2971,7 +3373,45 @@ function requireContentApi() {
2971
3373
  description: "Send email via MagicMail router"
2972
3374
  }
2973
3375
  },
2974
- // Public tracking endpoints (no auth required!)
3376
+ // ============= UNIFIED MESSAGE ROUTE =============
3377
+ {
3378
+ method: "POST",
3379
+ path: "/send-message",
3380
+ handler: "controller.sendMessage",
3381
+ config: {
3382
+ auth: false,
3383
+ description: "Send message via Email or WhatsApp (unified API)"
3384
+ }
3385
+ },
3386
+ // ============= WHATSAPP ROUTES =============
3387
+ {
3388
+ method: "POST",
3389
+ path: "/send-whatsapp",
3390
+ handler: "controller.sendWhatsApp",
3391
+ config: {
3392
+ auth: false,
3393
+ description: "Send WhatsApp message"
3394
+ }
3395
+ },
3396
+ {
3397
+ method: "GET",
3398
+ path: "/whatsapp/status",
3399
+ handler: "controller.getWhatsAppStatus",
3400
+ config: {
3401
+ auth: false,
3402
+ description: "Get WhatsApp connection status"
3403
+ }
3404
+ },
3405
+ {
3406
+ method: "GET",
3407
+ path: "/whatsapp/check/:phoneNumber",
3408
+ handler: "controller.checkWhatsAppNumber",
3409
+ config: {
3410
+ auth: false,
3411
+ description: "Check if phone number is on WhatsApp"
3412
+ }
3413
+ },
3414
+ // ============= TRACKING ROUTES =============
2975
3415
  {
2976
3416
  method: "GET",
2977
3417
  path: "/track/open/:emailId/:recipientHash",
@@ -3051,7 +3491,7 @@ function requireEncryption() {
3051
3491
  let encrypted = cipher.update(jsonData, "utf8", "hex");
3052
3492
  encrypted += cipher.final("hex");
3053
3493
  const authTag = cipher.getAuthTag();
3054
- return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
3494
+ return { encrypted: `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}` };
3055
3495
  } catch (err) {
3056
3496
  console.error("[magic-mail] Encryption failed:", err);
3057
3497
  throw new Error("Failed to encrypt credentials");
@@ -3061,7 +3501,8 @@ function requireEncryption() {
3061
3501
  if (!encryptedData) return null;
3062
3502
  try {
3063
3503
  const key = getEncryptionKey();
3064
- const parts = encryptedData.split(":");
3504
+ const encryptedString = typeof encryptedData === "object" && encryptedData.encrypted ? encryptedData.encrypted : encryptedData;
3505
+ const parts = encryptedString.split(":");
3065
3506
  if (parts.length !== 3) {
3066
3507
  throw new Error("Invalid encrypted data format");
3067
3508
  }
@@ -3135,7 +3576,7 @@ function requireEmailRouter() {
3135
3576
  let templateRecord = null;
3136
3577
  if (emailData.templateReferenceId) {
3137
3578
  resolvedTemplateReferenceId = String(emailData.templateReferenceId).trim();
3138
- strapi2.log.info(`[magic-mail] 🧩 Using provided templateReferenceId="${resolvedTemplateReferenceId}"`);
3579
+ strapi2.log.info(`[magic-mail] [TEMPLATE] Using provided templateReferenceId="${resolvedTemplateReferenceId}"`);
3139
3580
  }
3140
3581
  if (!resolvedTemplateReferenceId && templateId) {
3141
3582
  const numericTemplateId = Number(templateId);
@@ -3155,7 +3596,7 @@ function requireEmailRouter() {
3155
3596
  );
3156
3597
  } else {
3157
3598
  resolvedTemplateReferenceId = String(templateId).trim();
3158
- strapi2.log.info(`[magic-mail] 🧩 Treating templateId value as referenceId="${resolvedTemplateReferenceId}"`);
3599
+ strapi2.log.info(`[magic-mail] [TEMPLATE] Treating templateId value as referenceId="${resolvedTemplateReferenceId}"`);
3159
3600
  }
3160
3601
  }
3161
3602
  if (!resolvedTemplateReferenceId) {
@@ -3216,6 +3657,39 @@ function requireEmailRouter() {
3216
3657
  emailData.html = html;
3217
3658
  emailData.text = text;
3218
3659
  emailData.subject = subject;
3660
+ let matchedRule = null;
3661
+ try {
3662
+ const allRules = await strapi2.documents("plugin::magic-mail.routing-rule").findMany({
3663
+ filters: { isActive: true },
3664
+ sort: [{ priority: "desc" }]
3665
+ });
3666
+ for (const rule of allRules) {
3667
+ let matches = false;
3668
+ switch (rule.matchType) {
3669
+ case "emailType":
3670
+ matches = rule.matchValue === type;
3671
+ break;
3672
+ case "recipient":
3673
+ matches = to && to.toLowerCase().includes(rule.matchValue.toLowerCase());
3674
+ break;
3675
+ case "subject":
3676
+ matches = subject && subject.toLowerCase().includes(rule.matchValue.toLowerCase());
3677
+ break;
3678
+ case "template":
3679
+ matches = emailData.template && emailData.template === rule.matchValue;
3680
+ break;
3681
+ case "custom":
3682
+ matches = emailData.customField && emailData.customField === rule.matchValue;
3683
+ break;
3684
+ }
3685
+ if (matches) {
3686
+ matchedRule = rule;
3687
+ break;
3688
+ }
3689
+ }
3690
+ } catch (ruleError) {
3691
+ strapi2.log.warn("[magic-mail] [WARNING] Failed to check routing rules for WhatsApp fallback:", ruleError.message);
3692
+ }
3219
3693
  try {
3220
3694
  const licenseGuard2 = strapi2.plugin("magic-mail").service("license-guard");
3221
3695
  if (priority === "high") {
@@ -3235,7 +3709,7 @@ function requireEmailRouter() {
3235
3709
  }
3236
3710
  const canSend = await this.checkRateLimits(account);
3237
3711
  if (!canSend) {
3238
- const fallbackAccount = await this.selectAccount(type, priority, [account.id], emailData);
3712
+ const fallbackAccount = await this.selectAccount(type, priority, [account.documentId], emailData);
3239
3713
  if (fallbackAccount) {
3240
3714
  strapi2.log.info(`[magic-mail] Rate limit hit on ${account.name}, using fallback: ${fallbackAccount.name}`);
3241
3715
  return await this.sendViaAccount(fallbackAccount, emailData);
@@ -3266,18 +3740,56 @@ function requireEmailRouter() {
3266
3740
  };
3267
3741
  } catch (error) {
3268
3742
  strapi2.log.error("[magic-mail] [ERROR] Email send failed:", error);
3743
+ if (matchedRule?.whatsappFallback) {
3744
+ strapi2.log.info("[magic-mail] [FALLBACK] Email failed, attempting WhatsApp fallback...");
3745
+ try {
3746
+ const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
3747
+ const whatsappStatus = whatsapp2.getStatus();
3748
+ if (whatsappStatus.isConnected) {
3749
+ const phoneNumber = emailData.phoneNumber || emailData.whatsappPhone;
3750
+ if (phoneNumber) {
3751
+ const whatsappMessage = `*${subject}*
3752
+
3753
+ ${text || "Email delivery failed. Please check your email settings."}`;
3754
+ const waResult = await whatsapp2.sendMessage(phoneNumber, whatsappMessage);
3755
+ if (waResult.success) {
3756
+ strapi2.log.info(`[magic-mail] [SUCCESS] WhatsApp fallback sent to ${phoneNumber}`);
3757
+ return {
3758
+ success: true,
3759
+ fallbackUsed: "whatsapp",
3760
+ phoneNumber
3761
+ };
3762
+ } else {
3763
+ strapi2.log.warn("[magic-mail] [WARNING] WhatsApp fallback failed:", waResult.error);
3764
+ }
3765
+ } else {
3766
+ strapi2.log.warn("[magic-mail] [WARNING] WhatsApp fallback enabled but no phone number provided");
3767
+ }
3768
+ } else {
3769
+ strapi2.log.warn("[magic-mail] [WARNING] WhatsApp fallback enabled but WhatsApp not connected");
3770
+ }
3771
+ } catch (waError) {
3772
+ strapi2.log.error("[magic-mail] [ERROR] WhatsApp fallback error:", waError.message);
3773
+ }
3774
+ }
3269
3775
  throw error;
3270
3776
  }
3271
3777
  },
3272
3778
  /**
3273
3779
  * Select best account based on rules
3780
+ * @param {string} type - Email type (transactional, marketing, notification)
3781
+ * @param {string} priority - Priority level (high, normal, low)
3782
+ * @param {Array<string>} excludeDocumentIds - Array of documentIds to exclude from selection
3783
+ * @param {Object} emailData - Email data for routing rule matching
3784
+ * @returns {Promise<Object|null>} Selected account or null
3274
3785
  */
3275
- async selectAccount(type, priority, excludeIds = [], emailData = {}) {
3786
+ async selectAccount(type, priority, excludeDocumentIds = [], emailData = {}) {
3787
+ const filters = { isActive: true };
3788
+ if (excludeDocumentIds.length > 0) {
3789
+ filters.documentId = { $notIn: excludeDocumentIds };
3790
+ }
3276
3791
  const accounts2 = await strapi2.documents("plugin::magic-mail.email-account").findMany({
3277
- filters: {
3278
- isActive: true,
3279
- id: { $notIn: excludeIds }
3280
- },
3792
+ filters,
3281
3793
  sort: [{ priority: "desc" }]
3282
3794
  });
3283
3795
  if (!accounts2 || accounts2.length === 0) {
@@ -4174,6 +4686,168 @@ function requireEmailRouter() {
4174
4686
  ...headers
4175
4687
  }
4176
4688
  };
4689
+ },
4690
+ // ============================================================================
4691
+ // UNIFIED MESSAGE API - Send via Email OR WhatsApp
4692
+ // ============================================================================
4693
+ /**
4694
+ * Send a message via WhatsApp
4695
+ * Same pattern as send() but for WhatsApp
4696
+ * @param {Object} messageData - { phoneNumber, message, templateId, templateData }
4697
+ * @returns {Promise<Object>} Send result
4698
+ */
4699
+ async sendWhatsApp(messageData) {
4700
+ const {
4701
+ phoneNumber,
4702
+ message,
4703
+ templateId = null,
4704
+ templateData = {}
4705
+ } = messageData;
4706
+ if (!phoneNumber) {
4707
+ throw new Error("Phone number is required for WhatsApp messages");
4708
+ }
4709
+ const cleanPhone = phoneNumber.replace(/[^\d]/g, "");
4710
+ if (cleanPhone.length < 10) {
4711
+ throw new Error("Invalid phone number format. Use format: 491234567890 (country code + number)");
4712
+ }
4713
+ const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
4714
+ const status = whatsapp2.getStatus();
4715
+ if (!status.isConnected) {
4716
+ throw new Error("WhatsApp is not connected. Please connect WhatsApp first in the admin panel.");
4717
+ }
4718
+ let finalMessage = message;
4719
+ if (templateId) {
4720
+ try {
4721
+ const template = await whatsapp2.getTemplate(templateId);
4722
+ if (template) {
4723
+ finalMessage = template.content;
4724
+ Object.keys(templateData).forEach((key) => {
4725
+ finalMessage = finalMessage.replace(new RegExp(`{{${key}}}`, "g"), templateData[key]);
4726
+ });
4727
+ }
4728
+ } catch (error) {
4729
+ strapi2.log.warn(`[magic-mail] WhatsApp template ${templateId} not found, using plain message`);
4730
+ }
4731
+ }
4732
+ if (!finalMessage) {
4733
+ throw new Error("Message content is required");
4734
+ }
4735
+ strapi2.log.info(`[magic-mail] [WHATSAPP] Sending message to ${cleanPhone}`);
4736
+ const result = await whatsapp2.sendMessage(cleanPhone, finalMessage);
4737
+ if (result.success) {
4738
+ strapi2.log.info(`[magic-mail] [SUCCESS] WhatsApp message sent to ${cleanPhone}`);
4739
+ return {
4740
+ success: true,
4741
+ channel: "whatsapp",
4742
+ phoneNumber: cleanPhone,
4743
+ jid: result.jid
4744
+ };
4745
+ } else {
4746
+ strapi2.log.error(`[magic-mail] [ERROR] WhatsApp send failed: ${result.error}`);
4747
+ throw new Error(result.error || "Failed to send WhatsApp message");
4748
+ }
4749
+ },
4750
+ /**
4751
+ * Unified send method - automatically chooses Email or WhatsApp
4752
+ * @param {Object} messageData - Combined email and WhatsApp data
4753
+ * @param {string} messageData.channel - 'email' | 'whatsapp' | 'auto' (default: 'auto')
4754
+ * @param {string} messageData.to - Email address (for email channel)
4755
+ * @param {string} messageData.phoneNumber - Phone number (for whatsapp channel)
4756
+ * @param {string} messageData.subject - Email subject
4757
+ * @param {string} messageData.message - Plain text message (used for WhatsApp, or as email text)
4758
+ * @param {string} messageData.html - HTML content (email only)
4759
+ * @param {string} messageData.templateId - Template ID (works for both channels)
4760
+ * @param {Object} messageData.templateData - Template variables
4761
+ * @returns {Promise<Object>} Send result with channel info
4762
+ */
4763
+ async sendMessage(messageData) {
4764
+ const {
4765
+ channel = "auto",
4766
+ to,
4767
+ phoneNumber,
4768
+ subject,
4769
+ message,
4770
+ text,
4771
+ html,
4772
+ templateId,
4773
+ templateData,
4774
+ ...rest
4775
+ } = messageData;
4776
+ let useChannel = channel;
4777
+ if (channel === "auto") {
4778
+ if (to && to.includes("@")) {
4779
+ useChannel = "email";
4780
+ } else if (phoneNumber) {
4781
+ useChannel = "whatsapp";
4782
+ } else {
4783
+ throw new Error("Either email (to) or phoneNumber is required");
4784
+ }
4785
+ }
4786
+ strapi2.log.info(`[magic-mail] [SEND] Channel: ${useChannel}, to: ${to || phoneNumber}`);
4787
+ if (useChannel === "whatsapp") {
4788
+ if (!phoneNumber) {
4789
+ throw new Error("Phone number is required for WhatsApp channel");
4790
+ }
4791
+ return await this.sendWhatsApp({
4792
+ phoneNumber,
4793
+ message: message || text || subject,
4794
+ // Use message, fallback to text or subject
4795
+ templateId,
4796
+ templateData
4797
+ });
4798
+ } else {
4799
+ if (!to) {
4800
+ throw new Error("Email address (to) is required for email channel");
4801
+ }
4802
+ const result = await this.send({
4803
+ to,
4804
+ subject,
4805
+ text: text || message,
4806
+ html,
4807
+ templateId,
4808
+ templateData,
4809
+ phoneNumber,
4810
+ // Pass for WhatsApp fallback
4811
+ ...rest
4812
+ });
4813
+ return {
4814
+ ...result,
4815
+ channel: "email"
4816
+ };
4817
+ }
4818
+ },
4819
+ /**
4820
+ * Check WhatsApp connection status
4821
+ * @returns {Object} Connection status
4822
+ */
4823
+ getWhatsAppStatus() {
4824
+ try {
4825
+ const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
4826
+ return whatsapp2.getStatus();
4827
+ } catch (error) {
4828
+ return {
4829
+ isConnected: false,
4830
+ status: "unavailable",
4831
+ error: error.message
4832
+ };
4833
+ }
4834
+ },
4835
+ /**
4836
+ * Check if a phone number is registered on WhatsApp
4837
+ * @param {string} phoneNumber - Phone number to check
4838
+ * @returns {Promise<Object>} Check result
4839
+ */
4840
+ async checkWhatsAppNumber(phoneNumber) {
4841
+ try {
4842
+ const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
4843
+ return await whatsapp2.checkNumber(phoneNumber);
4844
+ } catch (error) {
4845
+ return {
4846
+ success: false,
4847
+ exists: false,
4848
+ error: error.message
4849
+ };
4850
+ }
4177
4851
  }
4178
4852
  });
4179
4853
  return emailRouter;
@@ -4870,7 +5544,7 @@ function requireOauth() {
4870
5544
  });
4871
5545
  return oauth;
4872
5546
  }
4873
- const version = "2.2.5";
5547
+ const version = "2.3.0";
4874
5548
  const require$$2 = {
4875
5549
  version
4876
5550
  };
@@ -6038,7 +6712,7 @@ function requireAnalytics() {
6038
6712
  const randomToken = crypto.randomBytes(8).toString("hex");
6039
6713
  const trackingUrl = `${baseUrl}/api/magic-mail/track/open/${emailId}/${recipientHash}?r=${randomToken}`;
6040
6714
  const trackingPixel = `<img src="${trackingUrl}" width="1" height="1" style="display:none;" alt="" />`;
6041
- strapi2.log.info(`[magic-mail] 📍 Tracking pixel URL: ${trackingUrl}`);
6715
+ strapi2.log.info(`[magic-mail] [PIXEL] Tracking pixel URL: ${trackingUrl}`);
6042
6716
  if (html.includes("</body>")) {
6043
6717
  return html.replace("</body>", `${trackingPixel}</body>`);
6044
6718
  }
@@ -6174,6 +6848,434 @@ function requireAnalytics() {
6174
6848
  });
6175
6849
  return analytics;
6176
6850
  }
6851
+ var whatsapp;
6852
+ var hasRequiredWhatsapp;
6853
+ function requireWhatsapp() {
6854
+ if (hasRequiredWhatsapp) return whatsapp;
6855
+ hasRequiredWhatsapp = 1;
6856
+ const path = require$$0__default$3.default;
6857
+ const fs = require$$1__default$2.default;
6858
+ let baileys = null;
6859
+ const loadBaileys = async () => {
6860
+ if (!baileys) {
6861
+ try {
6862
+ baileys = require("@whiskeysockets/baileys");
6863
+ if (process.env.DEBUG) {
6864
+ console.log("[MagicMail WhatsApp] Baileys loaded successfully");
6865
+ }
6866
+ return true;
6867
+ } catch (error) {
6868
+ console.warn("[MagicMail WhatsApp] Baileys not installed. WhatsApp features disabled.");
6869
+ console.warn("[MagicMail WhatsApp] Install with: npm install @whiskeysockets/baileys pino qrcode");
6870
+ return false;
6871
+ }
6872
+ }
6873
+ return true;
6874
+ };
6875
+ whatsapp = ({ strapi: strapi2 }) => {
6876
+ let sock = null;
6877
+ let qrCode = null;
6878
+ let connectionStatus = "disconnected";
6879
+ let lastError = null;
6880
+ let eventListeners = [];
6881
+ let wasConnectedBefore = false;
6882
+ let reconnectAttempts = 0;
6883
+ const MAX_RECONNECT_ATTEMPTS = 3;
6884
+ const isDebugEnabled = async () => {
6885
+ try {
6886
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
6887
+ const settings = await pluginStore.get({ key: "settings" });
6888
+ return settings?.whatsapp_debug === true;
6889
+ } catch {
6890
+ return false;
6891
+ }
6892
+ };
6893
+ const debugLog = async (message) => {
6894
+ if (await isDebugEnabled()) {
6895
+ strapi2.log.info(message);
6896
+ }
6897
+ };
6898
+ const getAuthPath = () => {
6899
+ const strapiRoot = strapi2.dirs?.app?.root || process.cwd();
6900
+ return path.join(strapiRoot, ".magicmail-whatsapp-auth");
6901
+ };
6902
+ const emit = (event, data) => {
6903
+ eventListeners.forEach((listener) => {
6904
+ try {
6905
+ listener(event, data);
6906
+ } catch (e) {
6907
+ console.error("[MagicMail WhatsApp] Event listener error:", e);
6908
+ }
6909
+ });
6910
+ };
6911
+ const service = {
6912
+ /**
6913
+ * Check if Baileys is available
6914
+ * @returns {Promise<boolean>} True if Baileys is installed
6915
+ */
6916
+ async isAvailable() {
6917
+ return await loadBaileys();
6918
+ },
6919
+ /**
6920
+ * Get current connection status
6921
+ * @returns {object} Status object with status, qrCode, lastError, isConnected
6922
+ */
6923
+ getStatus() {
6924
+ return {
6925
+ status: connectionStatus,
6926
+ qrCode,
6927
+ lastError,
6928
+ isConnected: connectionStatus === "connected"
6929
+ };
6930
+ },
6931
+ /**
6932
+ * Add event listener for WhatsApp events
6933
+ * @param {function} callback - Callback function(event, data)
6934
+ * @returns {function} Unsubscribe function
6935
+ */
6936
+ on(callback) {
6937
+ eventListeners.push(callback);
6938
+ return () => {
6939
+ eventListeners = eventListeners.filter((l) => l !== callback);
6940
+ };
6941
+ },
6942
+ /**
6943
+ * Initialize WhatsApp connection
6944
+ * @returns {Promise<object>} Connection result with success status
6945
+ */
6946
+ async connect() {
6947
+ const available = await loadBaileys();
6948
+ if (!available) {
6949
+ lastError = "Baileys not installed. Run: npm install @whiskeysockets/baileys pino qrcode";
6950
+ strapi2.log.error("[MagicMail WhatsApp] [ERROR] Baileys library not available");
6951
+ return { success: false, error: lastError };
6952
+ }
6953
+ if (sock && connectionStatus === "connected") {
6954
+ await debugLog("[MagicMail WhatsApp] Already connected");
6955
+ return { success: true, status: "already_connected" };
6956
+ }
6957
+ if (sock) {
6958
+ try {
6959
+ sock.end();
6960
+ } catch (e) {
6961
+ }
6962
+ sock = null;
6963
+ }
6964
+ return new Promise(async (resolve) => {
6965
+ try {
6966
+ connectionStatus = "connecting";
6967
+ emit("status", { status: connectionStatus });
6968
+ await debugLog("[MagicMail WhatsApp] Starting connection...");
6969
+ const authPath = getAuthPath();
6970
+ if (!fs.existsSync(authPath)) {
6971
+ fs.mkdirSync(authPath, { recursive: true });
6972
+ }
6973
+ await debugLog(`[MagicMail WhatsApp] Auth path: ${authPath}`);
6974
+ const { state, saveCreds } = await baileys.useMultiFileAuthState(authPath);
6975
+ await debugLog("[MagicMail WhatsApp] Auth state loaded");
6976
+ const pino = require("pino");
6977
+ const logger2 = pino({ level: "silent" });
6978
+ await debugLog("[MagicMail WhatsApp] Creating WhatsApp socket...");
6979
+ const makeSocket = baileys.default || baileys.makeWASocket;
6980
+ const browserConfig = baileys.Browsers.ubuntu("Chrome");
6981
+ await debugLog(`[MagicMail WhatsApp] Browser config: ${JSON.stringify(browserConfig)}`);
6982
+ sock = makeSocket({
6983
+ auth: state,
6984
+ logger: logger2,
6985
+ browser: browserConfig,
6986
+ syncFullHistory: false,
6987
+ markOnlineOnConnect: false,
6988
+ generateHighQualityLinkPreview: false,
6989
+ getMessage: async (key) => {
6990
+ return { conversation: "" };
6991
+ }
6992
+ });
6993
+ await debugLog("[MagicMail WhatsApp] Socket created, registering event handlers...");
6994
+ let resolved = false;
6995
+ const resolveOnce = (result) => {
6996
+ if (!resolved) {
6997
+ resolved = true;
6998
+ resolve(result);
6999
+ }
7000
+ };
7001
+ setTimeout(() => {
7002
+ if (!resolved) {
7003
+ strapi2.log.warn("[MagicMail WhatsApp] Connection timeout - no QR or connection");
7004
+ resolveOnce({ success: true, status: connectionStatus, qrCode });
7005
+ }
7006
+ }, 3e4);
7007
+ sock.ev.on("connection.update", async (update) => {
7008
+ await debugLog(`[MagicMail WhatsApp] connection.update: ${JSON.stringify(update)}`);
7009
+ const { connection, lastDisconnect, qr } = update;
7010
+ if (qr) {
7011
+ await debugLog("[MagicMail WhatsApp] QR code received");
7012
+ try {
7013
+ const QRCode = require("qrcode");
7014
+ qrCode = await QRCode.toDataURL(qr);
7015
+ connectionStatus = "qr_pending";
7016
+ emit("qr", { qrCode });
7017
+ emit("status", { status: connectionStatus });
7018
+ strapi2.log.info("[MagicMail WhatsApp] [SUCCESS] QR Code generated - scan with WhatsApp");
7019
+ resolveOnce({ success: true, status: connectionStatus, qrCode });
7020
+ } catch (qrError) {
7021
+ strapi2.log.error("[MagicMail WhatsApp] QR generation error:", qrError.message);
7022
+ }
7023
+ }
7024
+ if (connection === "close") {
7025
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
7026
+ const isLoggedOut = statusCode === baileys.DisconnectReason.loggedOut;
7027
+ const isRestartRequired = statusCode === baileys.DisconnectReason.restartRequired;
7028
+ const isConnectionFailure = statusCode === 405;
7029
+ await debugLog(`[MagicMail WhatsApp] Connection closed - statusCode: ${statusCode}`);
7030
+ if (isLoggedOut) {
7031
+ connectionStatus = "disconnected";
7032
+ lastError = "Logged out from WhatsApp";
7033
+ qrCode = null;
7034
+ wasConnectedBefore = false;
7035
+ reconnectAttempts = 0;
7036
+ try {
7037
+ fs.rmSync(authPath, { recursive: true, force: true });
7038
+ } catch (e) {
7039
+ }
7040
+ strapi2.log.warn("[MagicMail WhatsApp] Logged out - auth cleared");
7041
+ } else if (isRestartRequired) {
7042
+ await debugLog("[MagicMail WhatsApp] Restart required - reconnecting...");
7043
+ connectionStatus = "connecting";
7044
+ setTimeout(() => {
7045
+ service.connect();
7046
+ }, 1e3);
7047
+ } else if (isConnectionFailure && reconnectAttempts < 2) {
7048
+ reconnectAttempts++;
7049
+ await debugLog(`[MagicMail WhatsApp] Connection rejected (405) - retrying (${reconnectAttempts}/2)`);
7050
+ try {
7051
+ fs.rmSync(authPath, { recursive: true, force: true });
7052
+ } catch (e) {
7053
+ }
7054
+ connectionStatus = "disconnected";
7055
+ qrCode = null;
7056
+ setTimeout(() => {
7057
+ service.connect();
7058
+ }, 3e3);
7059
+ } else if (isConnectionFailure) {
7060
+ connectionStatus = "disconnected";
7061
+ lastError = "WhatsApp connection rejected (405). Please try again later.";
7062
+ strapi2.log.error("[MagicMail WhatsApp] [ERROR] Connection rejected after retries.");
7063
+ resolveOnce({ success: false, status: connectionStatus, error: lastError });
7064
+ } else if (wasConnectedBefore && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
7065
+ reconnectAttempts++;
7066
+ connectionStatus = "connecting";
7067
+ await debugLog(`[MagicMail WhatsApp] Reconnecting (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`);
7068
+ setTimeout(() => {
7069
+ service.connect();
7070
+ }, 3e3 * reconnectAttempts);
7071
+ } else if (!wasConnectedBefore) {
7072
+ connectionStatus = "disconnected";
7073
+ qrCode = null;
7074
+ await debugLog("[MagicMail WhatsApp] Connection closed - waiting for QR scan");
7075
+ } else {
7076
+ connectionStatus = "disconnected";
7077
+ lastError = "Max reconnect attempts reached";
7078
+ strapi2.log.warn("[MagicMail WhatsApp] Max reconnect attempts reached");
7079
+ }
7080
+ emit("status", { status: connectionStatus, error: lastError });
7081
+ }
7082
+ if (connection === "open") {
7083
+ connectionStatus = "connected";
7084
+ qrCode = null;
7085
+ lastError = null;
7086
+ wasConnectedBefore = true;
7087
+ reconnectAttempts = 0;
7088
+ emit("status", { status: connectionStatus });
7089
+ strapi2.log.info("[MagicMail WhatsApp] [SUCCESS] Connected successfully!");
7090
+ resolveOnce({ success: true, status: connectionStatus });
7091
+ }
7092
+ });
7093
+ sock.ev.on("creds.update", saveCreds);
7094
+ } catch (error) {
7095
+ lastError = error.message;
7096
+ connectionStatus = "disconnected";
7097
+ strapi2.log.error("[MagicMail WhatsApp] Connection error:", error);
7098
+ resolve({ success: false, error: error.message });
7099
+ }
7100
+ });
7101
+ },
7102
+ /**
7103
+ * Disconnect WhatsApp and clear session
7104
+ * @returns {Promise<object>} Result with success status
7105
+ */
7106
+ async disconnect() {
7107
+ if (sock) {
7108
+ try {
7109
+ await sock.logout();
7110
+ } catch (e) {
7111
+ }
7112
+ sock = null;
7113
+ }
7114
+ connectionStatus = "disconnected";
7115
+ qrCode = null;
7116
+ emit("status", { status: connectionStatus });
7117
+ strapi2.log.info("[MagicMail WhatsApp] Disconnected");
7118
+ return { success: true };
7119
+ },
7120
+ /**
7121
+ * Send a text message to a phone number
7122
+ * @param {string} phoneNumber - Phone number with country code (e.g., "491234567890")
7123
+ * @param {string} message - Message text
7124
+ * @returns {Promise<object>} Result with success status
7125
+ */
7126
+ async sendMessage(phoneNumber, message) {
7127
+ if (connectionStatus !== "connected" || !sock) {
7128
+ return {
7129
+ success: false,
7130
+ error: "WhatsApp not connected. Please connect first."
7131
+ };
7132
+ }
7133
+ try {
7134
+ const formattedNumber = phoneNumber.replace(/[^\d]/g, "");
7135
+ const jid = `${formattedNumber}@s.whatsapp.net`;
7136
+ const [exists] = await sock.onWhatsApp(formattedNumber);
7137
+ if (!exists?.exists) {
7138
+ return {
7139
+ success: false,
7140
+ error: `Phone number ${phoneNumber} is not registered on WhatsApp`
7141
+ };
7142
+ }
7143
+ await sock.sendMessage(jid, { text: message });
7144
+ await debugLog(`[MagicMail WhatsApp] Message sent to ${formattedNumber}`);
7145
+ return { success: true, jid };
7146
+ } catch (error) {
7147
+ strapi2.log.error("[MagicMail WhatsApp] Send error:", error);
7148
+ return { success: false, error: error.message };
7149
+ }
7150
+ },
7151
+ /**
7152
+ * Send message using a template
7153
+ * @param {string} phoneNumber - Phone number
7154
+ * @param {string} templateName - Template identifier
7155
+ * @param {object} variables - Template variables to replace
7156
+ * @returns {Promise<object>} Result with success status
7157
+ */
7158
+ async sendTemplateMessage(phoneNumber, templateName, variables = {}) {
7159
+ try {
7160
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
7161
+ const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
7162
+ let template = templates[templateName];
7163
+ if (!template) {
7164
+ template = `*{{subject}}*
7165
+
7166
+ {{body}}`;
7167
+ }
7168
+ let message = template;
7169
+ for (const [key, value] of Object.entries(variables)) {
7170
+ message = message.replace(new RegExp(`{{${key}}}`, "g"), value);
7171
+ }
7172
+ return this.sendMessage(phoneNumber, message);
7173
+ } catch (error) {
7174
+ return { success: false, error: error.message };
7175
+ }
7176
+ },
7177
+ /**
7178
+ * Check if a phone number is on WhatsApp
7179
+ * @param {string} phoneNumber - Phone number to check
7180
+ * @returns {Promise<object>} Result with exists boolean
7181
+ */
7182
+ async checkNumber(phoneNumber) {
7183
+ if (connectionStatus !== "connected" || !sock) {
7184
+ return { success: false, error: "WhatsApp not connected" };
7185
+ }
7186
+ try {
7187
+ const formattedNumber = phoneNumber.replace(/[^\d]/g, "");
7188
+ const [result] = await sock.onWhatsApp(formattedNumber);
7189
+ return {
7190
+ success: true,
7191
+ exists: result?.exists || false,
7192
+ jid: result?.jid
7193
+ };
7194
+ } catch (error) {
7195
+ return { success: false, error: error.message };
7196
+ }
7197
+ },
7198
+ /**
7199
+ * Get session info
7200
+ * @returns {Promise<object|null>} Session info or null if not connected
7201
+ */
7202
+ async getSessionInfo() {
7203
+ if (connectionStatus !== "connected" || !sock) {
7204
+ return null;
7205
+ }
7206
+ try {
7207
+ const user = sock.user;
7208
+ return {
7209
+ phoneNumber: user?.id?.split(":")[0] || user?.id?.split("@")[0],
7210
+ name: user?.name,
7211
+ platform: "WhatsApp Web"
7212
+ };
7213
+ } catch (error) {
7214
+ return null;
7215
+ }
7216
+ },
7217
+ /**
7218
+ * Reset connection state (for manual cleanup)
7219
+ */
7220
+ reset() {
7221
+ sock = null;
7222
+ qrCode = null;
7223
+ connectionStatus = "disconnected";
7224
+ lastError = null;
7225
+ wasConnectedBefore = false;
7226
+ reconnectAttempts = 0;
7227
+ },
7228
+ /**
7229
+ * Save WhatsApp template
7230
+ * @param {string} templateName - Template identifier
7231
+ * @param {string} templateContent - Template content with {{variables}}
7232
+ * @returns {Promise<object>} Result with success status
7233
+ */
7234
+ async saveTemplate(templateName, templateContent) {
7235
+ try {
7236
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
7237
+ const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
7238
+ templates[templateName] = templateContent;
7239
+ await pluginStore.set({ key: "whatsapp_templates", value: templates });
7240
+ return { success: true };
7241
+ } catch (error) {
7242
+ return { success: false, error: error.message };
7243
+ }
7244
+ },
7245
+ /**
7246
+ * Get all WhatsApp templates
7247
+ * @returns {Promise<object>} Templates object
7248
+ */
7249
+ async getTemplates() {
7250
+ try {
7251
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
7252
+ const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
7253
+ return templates;
7254
+ } catch (error) {
7255
+ return {};
7256
+ }
7257
+ },
7258
+ /**
7259
+ * Delete a WhatsApp template
7260
+ * @param {string} templateName - Template identifier
7261
+ * @returns {Promise<object>} Result with success status
7262
+ */
7263
+ async deleteTemplate(templateName) {
7264
+ try {
7265
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
7266
+ const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
7267
+ delete templates[templateName];
7268
+ await pluginStore.set({ key: "whatsapp_templates", value: templates });
7269
+ return { success: true };
7270
+ } catch (error) {
7271
+ return { success: false, error: error.message };
7272
+ }
7273
+ }
7274
+ };
7275
+ return service;
7276
+ };
7277
+ return whatsapp;
7278
+ }
6177
7279
  var services;
6178
7280
  var hasRequiredServices;
6179
7281
  function requireServices() {
@@ -6185,13 +7287,15 @@ function requireServices() {
6185
7287
  const licenseGuard2 = requireLicenseGuard();
6186
7288
  const emailDesigner2 = requireEmailDesigner();
6187
7289
  const analytics2 = requireAnalytics();
7290
+ const whatsapp2 = requireWhatsapp();
6188
7291
  services = {
6189
7292
  "email-router": emailRouter2,
6190
7293
  "account-manager": accountManager2,
6191
7294
  oauth: oauth2,
6192
7295
  "license-guard": licenseGuard2,
6193
7296
  "email-designer": emailDesigner2,
6194
- analytics: analytics2
7297
+ analytics: analytics2,
7298
+ whatsapp: whatsapp2
6195
7299
  };
6196
7300
  return services;
6197
7301
  }