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.
@@ -4,6 +4,8 @@ import require$$1$1 from "os";
4
4
  import require$$0$3 from "mustache";
5
5
  import require$$1$2 from "html-to-text";
6
6
  import require$$2$2 from "decode-html";
7
+ import require$$0$4 from "path";
8
+ import require$$1$3 from "fs";
7
9
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
8
10
  function getDefaultExportFromCjs(x2) {
9
11
  return x2 && x2.__esModule && Object.prototype.hasOwnProperty.call(x2, "default") ? x2["default"] : x2;
@@ -257,7 +259,7 @@ const collectionName$5 = "magic_mail_routing_rules";
257
259
  const info$5 = { "singularName": "routing-rule", "pluralName": "routing-rules", "displayName": "Email Routing Rule", "description": "Rules for routing emails to specific accounts" };
258
260
  const options$5 = { "draftAndPublish": false };
259
261
  const pluginOptions$5 = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
260
- 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" } };
262
+ 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" } };
261
263
  const require$$1 = {
262
264
  kind: kind$5,
263
265
  collectionName: collectionName$5,
@@ -394,6 +396,88 @@ function requireController() {
394
396
  strapi.log.error("[magic-mail] Error sending email:", err);
395
397
  ctx.throw(500, err.message || "Failed to send email");
396
398
  }
399
+ },
400
+ /**
401
+ * Send message via Email or WhatsApp (unified API)
402
+ * POST /api/magic-mail/send-message
403
+ * Body: { channel: 'email' | 'whatsapp' | 'auto', to, phoneNumber, subject, message, ... }
404
+ */
405
+ async sendMessage(ctx) {
406
+ try {
407
+ const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
408
+ const result = await emailRouter2.sendMessage(ctx.request.body);
409
+ ctx.body = {
410
+ success: true,
411
+ ...result
412
+ };
413
+ } catch (err) {
414
+ strapi.log.error("[magic-mail] Error sending message:", err);
415
+ ctx.throw(500, err.message || "Failed to send message");
416
+ }
417
+ },
418
+ /**
419
+ * Send WhatsApp message
420
+ * POST /api/magic-mail/send-whatsapp
421
+ * Body: { phoneNumber, message, templateId?, templateData? }
422
+ */
423
+ async sendWhatsApp(ctx) {
424
+ try {
425
+ const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
426
+ const result = await emailRouter2.sendWhatsApp(ctx.request.body);
427
+ ctx.body = {
428
+ success: true,
429
+ ...result
430
+ };
431
+ } catch (err) {
432
+ strapi.log.error("[magic-mail] Error sending WhatsApp:", err);
433
+ ctx.throw(500, err.message || "Failed to send WhatsApp message");
434
+ }
435
+ },
436
+ /**
437
+ * Get WhatsApp connection status
438
+ * GET /api/magic-mail/whatsapp/status
439
+ */
440
+ async getWhatsAppStatus(ctx) {
441
+ try {
442
+ const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
443
+ const status = emailRouter2.getWhatsAppStatus();
444
+ ctx.body = {
445
+ success: true,
446
+ data: status
447
+ };
448
+ } catch (err) {
449
+ strapi.log.error("[magic-mail] Error getting WhatsApp status:", err);
450
+ ctx.body = {
451
+ success: false,
452
+ data: {
453
+ isConnected: false,
454
+ status: "error",
455
+ error: err.message
456
+ }
457
+ };
458
+ }
459
+ },
460
+ /**
461
+ * Check if phone number is on WhatsApp
462
+ * GET /api/magic-mail/whatsapp/check/:phoneNumber
463
+ */
464
+ async checkWhatsAppNumber(ctx) {
465
+ try {
466
+ const { phoneNumber } = ctx.params;
467
+ if (!phoneNumber) {
468
+ ctx.throw(400, "Phone number is required");
469
+ return;
470
+ }
471
+ const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
472
+ const result = await emailRouter2.checkWhatsAppNumber(phoneNumber);
473
+ ctx.body = {
474
+ success: true,
475
+ data: result
476
+ };
477
+ } catch (err) {
478
+ strapi.log.error("[magic-mail] Error checking WhatsApp number:", err);
479
+ ctx.throw(500, err.message || "Failed to check phone number");
480
+ }
397
481
  }
398
482
  };
399
483
  return controller;
@@ -512,7 +596,7 @@ function requireAccounts() {
512
596
  if (!testEmail) {
513
597
  ctx.throw(400, "testEmail is required");
514
598
  }
515
- strapi.log.info("[magic-mail] 🧪 Testing Strapi Email Service integration...");
599
+ strapi.log.info("[magic-mail] [TEST] Testing Strapi Email Service integration...");
516
600
  strapi.log.info('[magic-mail] [EMAIL] Calling strapi.plugin("email").service("email").send()');
517
601
  if (accountName) {
518
602
  strapi.log.info(`[magic-mail] [FORCE] Forcing specific account: ${accountName}`);
@@ -2228,7 +2312,7 @@ function requireTest() {
2228
2312
  async testRelations(ctx) {
2229
2313
  try {
2230
2314
  console.log("\n" + "=".repeat(60));
2231
- console.log("🧪 TEST: Template Version Relations (Document Service API)");
2315
+ console.log("[TEST] Template - Version Relations (Document Service API)");
2232
2316
  console.log("=".repeat(60));
2233
2317
  let test1Success = false;
2234
2318
  let test1ReverseSuccess = false;
@@ -2396,7 +2480,7 @@ function requireTest() {
2396
2480
  } else {
2397
2481
  console.log(` [ERROR] FEHLER: Falsche Anzahl Versionen!`);
2398
2482
  }
2399
- console.log("\n🧹 Cleanup Test 3...");
2483
+ console.log("\n[CLEANUP] Cleanup Test 3...");
2400
2484
  if (afterSecondUpdate.versions) {
2401
2485
  for (const version3 of afterSecondUpdate.versions) {
2402
2486
  await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({ documentId: version3.documentId });
@@ -2414,7 +2498,7 @@ function requireTest() {
2414
2498
  console.log(`
2415
2499
  [INFO] Template: "${finalTemplate.name}" (documentId: ${finalTemplate.documentId})`);
2416
2500
  console.log(` Anzahl Versionen: ${finalTemplate.versions?.length || 0}`);
2417
- console.log("\n🧹 Aufräumen...");
2501
+ console.log("\n[CLEANUP] Aufraumen...");
2418
2502
  await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({ documentId: version1.documentId });
2419
2503
  await strapi.documents(EMAIL_TEMPLATE_VERSION_UID).delete({ documentId: version2.documentId });
2420
2504
  await strapi.documents(EMAIL_TEMPLATE_UID).delete({ documentId: testTemplate.documentId });
@@ -2444,6 +2528,219 @@ function requireTest() {
2444
2528
  };
2445
2529
  return test;
2446
2530
  }
2531
+ var whatsapp$1;
2532
+ var hasRequiredWhatsapp$1;
2533
+ function requireWhatsapp$1() {
2534
+ if (hasRequiredWhatsapp$1) return whatsapp$1;
2535
+ hasRequiredWhatsapp$1 = 1;
2536
+ whatsapp$1 = {
2537
+ /**
2538
+ * Check if WhatsApp/Baileys is available
2539
+ * GET /magic-mail/whatsapp/available
2540
+ */
2541
+ async checkAvailable(ctx) {
2542
+ try {
2543
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2544
+ const available = await whatsappService.isAvailable();
2545
+ ctx.body = {
2546
+ success: true,
2547
+ data: {
2548
+ available,
2549
+ message: available ? "WhatsApp integration is available" : "Baileys not installed. Run: npm install @whiskeysockets/baileys pino qrcode"
2550
+ }
2551
+ };
2552
+ } catch (error) {
2553
+ ctx.throw(500, error.message);
2554
+ }
2555
+ },
2556
+ /**
2557
+ * Get WhatsApp connection status
2558
+ * GET /magic-mail/whatsapp/status
2559
+ */
2560
+ async getStatus(ctx) {
2561
+ try {
2562
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2563
+ const status = whatsappService.getStatus();
2564
+ const sessionInfo = await whatsappService.getSessionInfo();
2565
+ ctx.body = {
2566
+ success: true,
2567
+ data: {
2568
+ ...status,
2569
+ session: sessionInfo
2570
+ }
2571
+ };
2572
+ } catch (error) {
2573
+ ctx.throw(500, error.message);
2574
+ }
2575
+ },
2576
+ /**
2577
+ * Connect to WhatsApp (generates QR code if needed)
2578
+ * POST /magic-mail/whatsapp/connect
2579
+ */
2580
+ async connect(ctx) {
2581
+ try {
2582
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2583
+ const result = await whatsappService.connect();
2584
+ ctx.body = {
2585
+ success: result.success,
2586
+ data: result
2587
+ };
2588
+ } catch (error) {
2589
+ ctx.throw(500, error.message);
2590
+ }
2591
+ },
2592
+ /**
2593
+ * Disconnect from WhatsApp
2594
+ * POST /magic-mail/whatsapp/disconnect
2595
+ */
2596
+ async disconnect(ctx) {
2597
+ try {
2598
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2599
+ const result = await whatsappService.disconnect();
2600
+ ctx.body = {
2601
+ success: result.success,
2602
+ data: result
2603
+ };
2604
+ } catch (error) {
2605
+ ctx.throw(500, error.message);
2606
+ }
2607
+ },
2608
+ /**
2609
+ * Send a test message
2610
+ * POST /magic-mail/whatsapp/send-test
2611
+ */
2612
+ async sendTest(ctx) {
2613
+ try {
2614
+ const { phoneNumber, message } = ctx.request.body;
2615
+ if (!phoneNumber) {
2616
+ return ctx.badRequest("Phone number is required");
2617
+ }
2618
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2619
+ const testMessage = message || `[MagicMail Test] This is a test message sent at ${(/* @__PURE__ */ new Date()).toLocaleString()}`;
2620
+ const result = await whatsappService.sendMessage(phoneNumber, testMessage);
2621
+ ctx.body = {
2622
+ success: result.success,
2623
+ data: result
2624
+ };
2625
+ } catch (error) {
2626
+ ctx.throw(500, error.message);
2627
+ }
2628
+ },
2629
+ /**
2630
+ * Send a message using a template
2631
+ * POST /magic-mail/whatsapp/send-template
2632
+ */
2633
+ async sendTemplateMessage(ctx) {
2634
+ try {
2635
+ const { phoneNumber, templateName, variables } = ctx.request.body;
2636
+ if (!phoneNumber || !templateName) {
2637
+ return ctx.badRequest("Phone number and template name are required");
2638
+ }
2639
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2640
+ const result = await whatsappService.sendTemplateMessage(phoneNumber, templateName, variables || {});
2641
+ ctx.body = {
2642
+ success: result.success,
2643
+ data: result
2644
+ };
2645
+ } catch (error) {
2646
+ ctx.throw(500, error.message);
2647
+ }
2648
+ },
2649
+ /**
2650
+ * Check if a phone number is on WhatsApp
2651
+ * POST /magic-mail/whatsapp/check-number
2652
+ */
2653
+ async checkNumber(ctx) {
2654
+ try {
2655
+ const { phoneNumber } = ctx.request.body;
2656
+ if (!phoneNumber) {
2657
+ return ctx.badRequest("Phone number is required");
2658
+ }
2659
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2660
+ const result = await whatsappService.checkNumber(phoneNumber);
2661
+ ctx.body = {
2662
+ success: result.success,
2663
+ data: result
2664
+ };
2665
+ } catch (error) {
2666
+ ctx.throw(500, error.message);
2667
+ }
2668
+ },
2669
+ /**
2670
+ * Get all WhatsApp templates
2671
+ * GET /magic-mail/whatsapp/templates
2672
+ */
2673
+ async getTemplates(ctx) {
2674
+ try {
2675
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2676
+ const templates = await whatsappService.getTemplates();
2677
+ ctx.body = {
2678
+ success: true,
2679
+ data: templates
2680
+ };
2681
+ } catch (error) {
2682
+ ctx.throw(500, error.message);
2683
+ }
2684
+ },
2685
+ /**
2686
+ * Save a WhatsApp template
2687
+ * POST /magic-mail/whatsapp/templates
2688
+ */
2689
+ async saveTemplate(ctx) {
2690
+ try {
2691
+ const { templateName, templateContent } = ctx.request.body;
2692
+ if (!templateName || !templateContent) {
2693
+ return ctx.badRequest("Template name and content are required");
2694
+ }
2695
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2696
+ const result = await whatsappService.saveTemplate(templateName, templateContent);
2697
+ ctx.body = {
2698
+ success: result.success,
2699
+ data: result
2700
+ };
2701
+ } catch (error) {
2702
+ ctx.throw(500, error.message);
2703
+ }
2704
+ },
2705
+ /**
2706
+ * Delete a WhatsApp template
2707
+ * DELETE /magic-mail/whatsapp/templates/:templateName
2708
+ */
2709
+ async deleteTemplate(ctx) {
2710
+ try {
2711
+ const { templateName } = ctx.params;
2712
+ if (!templateName) {
2713
+ return ctx.badRequest("Template name is required");
2714
+ }
2715
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2716
+ const result = await whatsappService.deleteTemplate(templateName);
2717
+ ctx.body = {
2718
+ success: result.success,
2719
+ data: result
2720
+ };
2721
+ } catch (error) {
2722
+ ctx.throw(500, error.message);
2723
+ }
2724
+ },
2725
+ /**
2726
+ * Get session info
2727
+ * GET /magic-mail/whatsapp/session
2728
+ */
2729
+ async getSession(ctx) {
2730
+ try {
2731
+ const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
2732
+ const sessionInfo = await whatsappService.getSessionInfo();
2733
+ ctx.body = {
2734
+ success: true,
2735
+ data: sessionInfo
2736
+ };
2737
+ } catch (error) {
2738
+ ctx.throw(500, error.message);
2739
+ }
2740
+ }
2741
+ };
2742
+ return whatsapp$1;
2743
+ }
2447
2744
  var controllers;
2448
2745
  var hasRequiredControllers;
2449
2746
  function requireControllers() {
@@ -2457,6 +2754,7 @@ function requireControllers() {
2457
2754
  const emailDesigner2 = requireEmailDesigner$1();
2458
2755
  const analytics2 = requireAnalytics$1();
2459
2756
  const test2 = requireTest();
2757
+ const whatsapp2 = requireWhatsapp$1();
2460
2758
  controllers = {
2461
2759
  controller: controller2,
2462
2760
  accounts: accounts2,
@@ -2465,7 +2763,8 @@ function requireControllers() {
2465
2763
  license: license2,
2466
2764
  emailDesigner: emailDesigner2,
2467
2765
  analytics: analytics2,
2468
- test: test2
2766
+ test: test2,
2767
+ whatsapp: whatsapp2
2469
2768
  };
2470
2769
  return controllers;
2471
2770
  }
@@ -2940,6 +3239,106 @@ function requireAdmin() {
2940
3239
  policies: ["admin::isAuthenticatedAdmin"],
2941
3240
  description: "Test template-version relations"
2942
3241
  }
3242
+ },
3243
+ // WhatsApp Routes
3244
+ {
3245
+ method: "GET",
3246
+ path: "/whatsapp/available",
3247
+ handler: "whatsapp.checkAvailable",
3248
+ config: {
3249
+ policies: ["admin::isAuthenticatedAdmin"],
3250
+ description: "Check if WhatsApp/Baileys is available"
3251
+ }
3252
+ },
3253
+ {
3254
+ method: "GET",
3255
+ path: "/whatsapp/status",
3256
+ handler: "whatsapp.getStatus",
3257
+ config: {
3258
+ policies: ["admin::isAuthenticatedAdmin"],
3259
+ description: "Get WhatsApp connection status"
3260
+ }
3261
+ },
3262
+ {
3263
+ method: "POST",
3264
+ path: "/whatsapp/connect",
3265
+ handler: "whatsapp.connect",
3266
+ config: {
3267
+ policies: ["admin::isAuthenticatedAdmin"],
3268
+ description: "Connect to WhatsApp (generates QR if needed)"
3269
+ }
3270
+ },
3271
+ {
3272
+ method: "POST",
3273
+ path: "/whatsapp/disconnect",
3274
+ handler: "whatsapp.disconnect",
3275
+ config: {
3276
+ policies: ["admin::isAuthenticatedAdmin"],
3277
+ description: "Disconnect from WhatsApp"
3278
+ }
3279
+ },
3280
+ {
3281
+ method: "POST",
3282
+ path: "/whatsapp/send-test",
3283
+ handler: "whatsapp.sendTest",
3284
+ config: {
3285
+ policies: ["admin::isAuthenticatedAdmin"],
3286
+ description: "Send a test WhatsApp message"
3287
+ }
3288
+ },
3289
+ {
3290
+ method: "POST",
3291
+ path: "/whatsapp/send-template",
3292
+ handler: "whatsapp.sendTemplateMessage",
3293
+ config: {
3294
+ policies: ["admin::isAuthenticatedAdmin"],
3295
+ description: "Send WhatsApp message using template"
3296
+ }
3297
+ },
3298
+ {
3299
+ method: "POST",
3300
+ path: "/whatsapp/check-number",
3301
+ handler: "whatsapp.checkNumber",
3302
+ config: {
3303
+ policies: ["admin::isAuthenticatedAdmin"],
3304
+ description: "Check if phone number is on WhatsApp"
3305
+ }
3306
+ },
3307
+ {
3308
+ method: "GET",
3309
+ path: "/whatsapp/templates",
3310
+ handler: "whatsapp.getTemplates",
3311
+ config: {
3312
+ policies: ["admin::isAuthenticatedAdmin"],
3313
+ description: "Get all WhatsApp message templates"
3314
+ }
3315
+ },
3316
+ {
3317
+ method: "POST",
3318
+ path: "/whatsapp/templates",
3319
+ handler: "whatsapp.saveTemplate",
3320
+ config: {
3321
+ policies: ["admin::isAuthenticatedAdmin"],
3322
+ description: "Save a WhatsApp message template"
3323
+ }
3324
+ },
3325
+ {
3326
+ method: "DELETE",
3327
+ path: "/whatsapp/templates/:templateName",
3328
+ handler: "whatsapp.deleteTemplate",
3329
+ config: {
3330
+ policies: ["admin::isAuthenticatedAdmin"],
3331
+ description: "Delete a WhatsApp message template"
3332
+ }
3333
+ },
3334
+ {
3335
+ method: "GET",
3336
+ path: "/whatsapp/session",
3337
+ handler: "whatsapp.getSession",
3338
+ config: {
3339
+ policies: ["admin::isAuthenticatedAdmin"],
3340
+ description: "Get WhatsApp session info"
3341
+ }
2943
3342
  }
2944
3343
  ]
2945
3344
  };
@@ -2953,6 +3352,7 @@ function requireContentApi() {
2953
3352
  contentApi = {
2954
3353
  type: "content-api",
2955
3354
  routes: [
3355
+ // ============= EMAIL ROUTES =============
2956
3356
  {
2957
3357
  method: "POST",
2958
3358
  path: "/send",
@@ -2963,7 +3363,45 @@ function requireContentApi() {
2963
3363
  description: "Send email via MagicMail router"
2964
3364
  }
2965
3365
  },
2966
- // Public tracking endpoints (no auth required!)
3366
+ // ============= UNIFIED MESSAGE ROUTE =============
3367
+ {
3368
+ method: "POST",
3369
+ path: "/send-message",
3370
+ handler: "controller.sendMessage",
3371
+ config: {
3372
+ auth: false,
3373
+ description: "Send message via Email or WhatsApp (unified API)"
3374
+ }
3375
+ },
3376
+ // ============= WHATSAPP ROUTES =============
3377
+ {
3378
+ method: "POST",
3379
+ path: "/send-whatsapp",
3380
+ handler: "controller.sendWhatsApp",
3381
+ config: {
3382
+ auth: false,
3383
+ description: "Send WhatsApp message"
3384
+ }
3385
+ },
3386
+ {
3387
+ method: "GET",
3388
+ path: "/whatsapp/status",
3389
+ handler: "controller.getWhatsAppStatus",
3390
+ config: {
3391
+ auth: false,
3392
+ description: "Get WhatsApp connection status"
3393
+ }
3394
+ },
3395
+ {
3396
+ method: "GET",
3397
+ path: "/whatsapp/check/:phoneNumber",
3398
+ handler: "controller.checkWhatsAppNumber",
3399
+ config: {
3400
+ auth: false,
3401
+ description: "Check if phone number is on WhatsApp"
3402
+ }
3403
+ },
3404
+ // ============= TRACKING ROUTES =============
2967
3405
  {
2968
3406
  method: "GET",
2969
3407
  path: "/track/open/:emailId/:recipientHash",
@@ -3043,7 +3481,7 @@ function requireEncryption() {
3043
3481
  let encrypted = cipher.update(jsonData, "utf8", "hex");
3044
3482
  encrypted += cipher.final("hex");
3045
3483
  const authTag = cipher.getAuthTag();
3046
- return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
3484
+ return { encrypted: `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}` };
3047
3485
  } catch (err) {
3048
3486
  console.error("[magic-mail] Encryption failed:", err);
3049
3487
  throw new Error("Failed to encrypt credentials");
@@ -3053,7 +3491,8 @@ function requireEncryption() {
3053
3491
  if (!encryptedData) return null;
3054
3492
  try {
3055
3493
  const key = getEncryptionKey();
3056
- const parts = encryptedData.split(":");
3494
+ const encryptedString = typeof encryptedData === "object" && encryptedData.encrypted ? encryptedData.encrypted : encryptedData;
3495
+ const parts = encryptedString.split(":");
3057
3496
  if (parts.length !== 3) {
3058
3497
  throw new Error("Invalid encrypted data format");
3059
3498
  }
@@ -3127,7 +3566,7 @@ function requireEmailRouter() {
3127
3566
  let templateRecord = null;
3128
3567
  if (emailData.templateReferenceId) {
3129
3568
  resolvedTemplateReferenceId = String(emailData.templateReferenceId).trim();
3130
- strapi2.log.info(`[magic-mail] 🧩 Using provided templateReferenceId="${resolvedTemplateReferenceId}"`);
3569
+ strapi2.log.info(`[magic-mail] [TEMPLATE] Using provided templateReferenceId="${resolvedTemplateReferenceId}"`);
3131
3570
  }
3132
3571
  if (!resolvedTemplateReferenceId && templateId) {
3133
3572
  const numericTemplateId = Number(templateId);
@@ -3147,7 +3586,7 @@ function requireEmailRouter() {
3147
3586
  );
3148
3587
  } else {
3149
3588
  resolvedTemplateReferenceId = String(templateId).trim();
3150
- strapi2.log.info(`[magic-mail] 🧩 Treating templateId value as referenceId="${resolvedTemplateReferenceId}"`);
3589
+ strapi2.log.info(`[magic-mail] [TEMPLATE] Treating templateId value as referenceId="${resolvedTemplateReferenceId}"`);
3151
3590
  }
3152
3591
  }
3153
3592
  if (!resolvedTemplateReferenceId) {
@@ -3208,6 +3647,39 @@ function requireEmailRouter() {
3208
3647
  emailData.html = html;
3209
3648
  emailData.text = text;
3210
3649
  emailData.subject = subject;
3650
+ let matchedRule = null;
3651
+ try {
3652
+ const allRules = await strapi2.documents("plugin::magic-mail.routing-rule").findMany({
3653
+ filters: { isActive: true },
3654
+ sort: [{ priority: "desc" }]
3655
+ });
3656
+ for (const rule of allRules) {
3657
+ let matches = false;
3658
+ switch (rule.matchType) {
3659
+ case "emailType":
3660
+ matches = rule.matchValue === type;
3661
+ break;
3662
+ case "recipient":
3663
+ matches = to && to.toLowerCase().includes(rule.matchValue.toLowerCase());
3664
+ break;
3665
+ case "subject":
3666
+ matches = subject && subject.toLowerCase().includes(rule.matchValue.toLowerCase());
3667
+ break;
3668
+ case "template":
3669
+ matches = emailData.template && emailData.template === rule.matchValue;
3670
+ break;
3671
+ case "custom":
3672
+ matches = emailData.customField && emailData.customField === rule.matchValue;
3673
+ break;
3674
+ }
3675
+ if (matches) {
3676
+ matchedRule = rule;
3677
+ break;
3678
+ }
3679
+ }
3680
+ } catch (ruleError) {
3681
+ strapi2.log.warn("[magic-mail] [WARNING] Failed to check routing rules for WhatsApp fallback:", ruleError.message);
3682
+ }
3211
3683
  try {
3212
3684
  const licenseGuard2 = strapi2.plugin("magic-mail").service("license-guard");
3213
3685
  if (priority === "high") {
@@ -3227,7 +3699,7 @@ function requireEmailRouter() {
3227
3699
  }
3228
3700
  const canSend = await this.checkRateLimits(account);
3229
3701
  if (!canSend) {
3230
- const fallbackAccount = await this.selectAccount(type, priority, [account.id], emailData);
3702
+ const fallbackAccount = await this.selectAccount(type, priority, [account.documentId], emailData);
3231
3703
  if (fallbackAccount) {
3232
3704
  strapi2.log.info(`[magic-mail] Rate limit hit on ${account.name}, using fallback: ${fallbackAccount.name}`);
3233
3705
  return await this.sendViaAccount(fallbackAccount, emailData);
@@ -3258,18 +3730,56 @@ function requireEmailRouter() {
3258
3730
  };
3259
3731
  } catch (error) {
3260
3732
  strapi2.log.error("[magic-mail] [ERROR] Email send failed:", error);
3733
+ if (matchedRule?.whatsappFallback) {
3734
+ strapi2.log.info("[magic-mail] [FALLBACK] Email failed, attempting WhatsApp fallback...");
3735
+ try {
3736
+ const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
3737
+ const whatsappStatus = whatsapp2.getStatus();
3738
+ if (whatsappStatus.isConnected) {
3739
+ const phoneNumber = emailData.phoneNumber || emailData.whatsappPhone;
3740
+ if (phoneNumber) {
3741
+ const whatsappMessage = `*${subject}*
3742
+
3743
+ ${text || "Email delivery failed. Please check your email settings."}`;
3744
+ const waResult = await whatsapp2.sendMessage(phoneNumber, whatsappMessage);
3745
+ if (waResult.success) {
3746
+ strapi2.log.info(`[magic-mail] [SUCCESS] WhatsApp fallback sent to ${phoneNumber}`);
3747
+ return {
3748
+ success: true,
3749
+ fallbackUsed: "whatsapp",
3750
+ phoneNumber
3751
+ };
3752
+ } else {
3753
+ strapi2.log.warn("[magic-mail] [WARNING] WhatsApp fallback failed:", waResult.error);
3754
+ }
3755
+ } else {
3756
+ strapi2.log.warn("[magic-mail] [WARNING] WhatsApp fallback enabled but no phone number provided");
3757
+ }
3758
+ } else {
3759
+ strapi2.log.warn("[magic-mail] [WARNING] WhatsApp fallback enabled but WhatsApp not connected");
3760
+ }
3761
+ } catch (waError) {
3762
+ strapi2.log.error("[magic-mail] [ERROR] WhatsApp fallback error:", waError.message);
3763
+ }
3764
+ }
3261
3765
  throw error;
3262
3766
  }
3263
3767
  },
3264
3768
  /**
3265
3769
  * Select best account based on rules
3770
+ * @param {string} type - Email type (transactional, marketing, notification)
3771
+ * @param {string} priority - Priority level (high, normal, low)
3772
+ * @param {Array<string>} excludeDocumentIds - Array of documentIds to exclude from selection
3773
+ * @param {Object} emailData - Email data for routing rule matching
3774
+ * @returns {Promise<Object|null>} Selected account or null
3266
3775
  */
3267
- async selectAccount(type, priority, excludeIds = [], emailData = {}) {
3776
+ async selectAccount(type, priority, excludeDocumentIds = [], emailData = {}) {
3777
+ const filters = { isActive: true };
3778
+ if (excludeDocumentIds.length > 0) {
3779
+ filters.documentId = { $notIn: excludeDocumentIds };
3780
+ }
3268
3781
  const accounts2 = await strapi2.documents("plugin::magic-mail.email-account").findMany({
3269
- filters: {
3270
- isActive: true,
3271
- id: { $notIn: excludeIds }
3272
- },
3782
+ filters,
3273
3783
  sort: [{ priority: "desc" }]
3274
3784
  });
3275
3785
  if (!accounts2 || accounts2.length === 0) {
@@ -4166,6 +4676,168 @@ function requireEmailRouter() {
4166
4676
  ...headers
4167
4677
  }
4168
4678
  };
4679
+ },
4680
+ // ============================================================================
4681
+ // UNIFIED MESSAGE API - Send via Email OR WhatsApp
4682
+ // ============================================================================
4683
+ /**
4684
+ * Send a message via WhatsApp
4685
+ * Same pattern as send() but for WhatsApp
4686
+ * @param {Object} messageData - { phoneNumber, message, templateId, templateData }
4687
+ * @returns {Promise<Object>} Send result
4688
+ */
4689
+ async sendWhatsApp(messageData) {
4690
+ const {
4691
+ phoneNumber,
4692
+ message,
4693
+ templateId = null,
4694
+ templateData = {}
4695
+ } = messageData;
4696
+ if (!phoneNumber) {
4697
+ throw new Error("Phone number is required for WhatsApp messages");
4698
+ }
4699
+ const cleanPhone = phoneNumber.replace(/[^\d]/g, "");
4700
+ if (cleanPhone.length < 10) {
4701
+ throw new Error("Invalid phone number format. Use format: 491234567890 (country code + number)");
4702
+ }
4703
+ const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
4704
+ const status = whatsapp2.getStatus();
4705
+ if (!status.isConnected) {
4706
+ throw new Error("WhatsApp is not connected. Please connect WhatsApp first in the admin panel.");
4707
+ }
4708
+ let finalMessage = message;
4709
+ if (templateId) {
4710
+ try {
4711
+ const template = await whatsapp2.getTemplate(templateId);
4712
+ if (template) {
4713
+ finalMessage = template.content;
4714
+ Object.keys(templateData).forEach((key) => {
4715
+ finalMessage = finalMessage.replace(new RegExp(`{{${key}}}`, "g"), templateData[key]);
4716
+ });
4717
+ }
4718
+ } catch (error) {
4719
+ strapi2.log.warn(`[magic-mail] WhatsApp template ${templateId} not found, using plain message`);
4720
+ }
4721
+ }
4722
+ if (!finalMessage) {
4723
+ throw new Error("Message content is required");
4724
+ }
4725
+ strapi2.log.info(`[magic-mail] [WHATSAPP] Sending message to ${cleanPhone}`);
4726
+ const result = await whatsapp2.sendMessage(cleanPhone, finalMessage);
4727
+ if (result.success) {
4728
+ strapi2.log.info(`[magic-mail] [SUCCESS] WhatsApp message sent to ${cleanPhone}`);
4729
+ return {
4730
+ success: true,
4731
+ channel: "whatsapp",
4732
+ phoneNumber: cleanPhone,
4733
+ jid: result.jid
4734
+ };
4735
+ } else {
4736
+ strapi2.log.error(`[magic-mail] [ERROR] WhatsApp send failed: ${result.error}`);
4737
+ throw new Error(result.error || "Failed to send WhatsApp message");
4738
+ }
4739
+ },
4740
+ /**
4741
+ * Unified send method - automatically chooses Email or WhatsApp
4742
+ * @param {Object} messageData - Combined email and WhatsApp data
4743
+ * @param {string} messageData.channel - 'email' | 'whatsapp' | 'auto' (default: 'auto')
4744
+ * @param {string} messageData.to - Email address (for email channel)
4745
+ * @param {string} messageData.phoneNumber - Phone number (for whatsapp channel)
4746
+ * @param {string} messageData.subject - Email subject
4747
+ * @param {string} messageData.message - Plain text message (used for WhatsApp, or as email text)
4748
+ * @param {string} messageData.html - HTML content (email only)
4749
+ * @param {string} messageData.templateId - Template ID (works for both channels)
4750
+ * @param {Object} messageData.templateData - Template variables
4751
+ * @returns {Promise<Object>} Send result with channel info
4752
+ */
4753
+ async sendMessage(messageData) {
4754
+ const {
4755
+ channel = "auto",
4756
+ to,
4757
+ phoneNumber,
4758
+ subject,
4759
+ message,
4760
+ text,
4761
+ html,
4762
+ templateId,
4763
+ templateData,
4764
+ ...rest
4765
+ } = messageData;
4766
+ let useChannel = channel;
4767
+ if (channel === "auto") {
4768
+ if (to && to.includes("@")) {
4769
+ useChannel = "email";
4770
+ } else if (phoneNumber) {
4771
+ useChannel = "whatsapp";
4772
+ } else {
4773
+ throw new Error("Either email (to) or phoneNumber is required");
4774
+ }
4775
+ }
4776
+ strapi2.log.info(`[magic-mail] [SEND] Channel: ${useChannel}, to: ${to || phoneNumber}`);
4777
+ if (useChannel === "whatsapp") {
4778
+ if (!phoneNumber) {
4779
+ throw new Error("Phone number is required for WhatsApp channel");
4780
+ }
4781
+ return await this.sendWhatsApp({
4782
+ phoneNumber,
4783
+ message: message || text || subject,
4784
+ // Use message, fallback to text or subject
4785
+ templateId,
4786
+ templateData
4787
+ });
4788
+ } else {
4789
+ if (!to) {
4790
+ throw new Error("Email address (to) is required for email channel");
4791
+ }
4792
+ const result = await this.send({
4793
+ to,
4794
+ subject,
4795
+ text: text || message,
4796
+ html,
4797
+ templateId,
4798
+ templateData,
4799
+ phoneNumber,
4800
+ // Pass for WhatsApp fallback
4801
+ ...rest
4802
+ });
4803
+ return {
4804
+ ...result,
4805
+ channel: "email"
4806
+ };
4807
+ }
4808
+ },
4809
+ /**
4810
+ * Check WhatsApp connection status
4811
+ * @returns {Object} Connection status
4812
+ */
4813
+ getWhatsAppStatus() {
4814
+ try {
4815
+ const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
4816
+ return whatsapp2.getStatus();
4817
+ } catch (error) {
4818
+ return {
4819
+ isConnected: false,
4820
+ status: "unavailable",
4821
+ error: error.message
4822
+ };
4823
+ }
4824
+ },
4825
+ /**
4826
+ * Check if a phone number is registered on WhatsApp
4827
+ * @param {string} phoneNumber - Phone number to check
4828
+ * @returns {Promise<Object>} Check result
4829
+ */
4830
+ async checkWhatsAppNumber(phoneNumber) {
4831
+ try {
4832
+ const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
4833
+ return await whatsapp2.checkNumber(phoneNumber);
4834
+ } catch (error) {
4835
+ return {
4836
+ success: false,
4837
+ exists: false,
4838
+ error: error.message
4839
+ };
4840
+ }
4169
4841
  }
4170
4842
  });
4171
4843
  return emailRouter;
@@ -4862,7 +5534,7 @@ function requireOauth() {
4862
5534
  });
4863
5535
  return oauth;
4864
5536
  }
4865
- const version = "2.2.5";
5537
+ const version = "2.3.0";
4866
5538
  const require$$2 = {
4867
5539
  version
4868
5540
  };
@@ -6030,7 +6702,7 @@ function requireAnalytics() {
6030
6702
  const randomToken = crypto.randomBytes(8).toString("hex");
6031
6703
  const trackingUrl = `${baseUrl}/api/magic-mail/track/open/${emailId}/${recipientHash}?r=${randomToken}`;
6032
6704
  const trackingPixel = `<img src="${trackingUrl}" width="1" height="1" style="display:none;" alt="" />`;
6033
- strapi2.log.info(`[magic-mail] 📍 Tracking pixel URL: ${trackingUrl}`);
6705
+ strapi2.log.info(`[magic-mail] [PIXEL] Tracking pixel URL: ${trackingUrl}`);
6034
6706
  if (html.includes("</body>")) {
6035
6707
  return html.replace("</body>", `${trackingPixel}</body>`);
6036
6708
  }
@@ -6166,6 +6838,434 @@ function requireAnalytics() {
6166
6838
  });
6167
6839
  return analytics;
6168
6840
  }
6841
+ var whatsapp;
6842
+ var hasRequiredWhatsapp;
6843
+ function requireWhatsapp() {
6844
+ if (hasRequiredWhatsapp) return whatsapp;
6845
+ hasRequiredWhatsapp = 1;
6846
+ const path = require$$0$4;
6847
+ const fs = require$$1$3;
6848
+ let baileys = null;
6849
+ const loadBaileys = async () => {
6850
+ if (!baileys) {
6851
+ try {
6852
+ baileys = require("@whiskeysockets/baileys");
6853
+ if (process.env.DEBUG) {
6854
+ console.log("[MagicMail WhatsApp] Baileys loaded successfully");
6855
+ }
6856
+ return true;
6857
+ } catch (error) {
6858
+ console.warn("[MagicMail WhatsApp] Baileys not installed. WhatsApp features disabled.");
6859
+ console.warn("[MagicMail WhatsApp] Install with: npm install @whiskeysockets/baileys pino qrcode");
6860
+ return false;
6861
+ }
6862
+ }
6863
+ return true;
6864
+ };
6865
+ whatsapp = ({ strapi: strapi2 }) => {
6866
+ let sock = null;
6867
+ let qrCode = null;
6868
+ let connectionStatus = "disconnected";
6869
+ let lastError = null;
6870
+ let eventListeners = [];
6871
+ let wasConnectedBefore = false;
6872
+ let reconnectAttempts = 0;
6873
+ const MAX_RECONNECT_ATTEMPTS = 3;
6874
+ const isDebugEnabled = async () => {
6875
+ try {
6876
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
6877
+ const settings = await pluginStore.get({ key: "settings" });
6878
+ return settings?.whatsapp_debug === true;
6879
+ } catch {
6880
+ return false;
6881
+ }
6882
+ };
6883
+ const debugLog = async (message) => {
6884
+ if (await isDebugEnabled()) {
6885
+ strapi2.log.info(message);
6886
+ }
6887
+ };
6888
+ const getAuthPath = () => {
6889
+ const strapiRoot = strapi2.dirs?.app?.root || process.cwd();
6890
+ return path.join(strapiRoot, ".magicmail-whatsapp-auth");
6891
+ };
6892
+ const emit = (event, data) => {
6893
+ eventListeners.forEach((listener) => {
6894
+ try {
6895
+ listener(event, data);
6896
+ } catch (e) {
6897
+ console.error("[MagicMail WhatsApp] Event listener error:", e);
6898
+ }
6899
+ });
6900
+ };
6901
+ const service = {
6902
+ /**
6903
+ * Check if Baileys is available
6904
+ * @returns {Promise<boolean>} True if Baileys is installed
6905
+ */
6906
+ async isAvailable() {
6907
+ return await loadBaileys();
6908
+ },
6909
+ /**
6910
+ * Get current connection status
6911
+ * @returns {object} Status object with status, qrCode, lastError, isConnected
6912
+ */
6913
+ getStatus() {
6914
+ return {
6915
+ status: connectionStatus,
6916
+ qrCode,
6917
+ lastError,
6918
+ isConnected: connectionStatus === "connected"
6919
+ };
6920
+ },
6921
+ /**
6922
+ * Add event listener for WhatsApp events
6923
+ * @param {function} callback - Callback function(event, data)
6924
+ * @returns {function} Unsubscribe function
6925
+ */
6926
+ on(callback) {
6927
+ eventListeners.push(callback);
6928
+ return () => {
6929
+ eventListeners = eventListeners.filter((l) => l !== callback);
6930
+ };
6931
+ },
6932
+ /**
6933
+ * Initialize WhatsApp connection
6934
+ * @returns {Promise<object>} Connection result with success status
6935
+ */
6936
+ async connect() {
6937
+ const available = await loadBaileys();
6938
+ if (!available) {
6939
+ lastError = "Baileys not installed. Run: npm install @whiskeysockets/baileys pino qrcode";
6940
+ strapi2.log.error("[MagicMail WhatsApp] [ERROR] Baileys library not available");
6941
+ return { success: false, error: lastError };
6942
+ }
6943
+ if (sock && connectionStatus === "connected") {
6944
+ await debugLog("[MagicMail WhatsApp] Already connected");
6945
+ return { success: true, status: "already_connected" };
6946
+ }
6947
+ if (sock) {
6948
+ try {
6949
+ sock.end();
6950
+ } catch (e) {
6951
+ }
6952
+ sock = null;
6953
+ }
6954
+ return new Promise(async (resolve) => {
6955
+ try {
6956
+ connectionStatus = "connecting";
6957
+ emit("status", { status: connectionStatus });
6958
+ await debugLog("[MagicMail WhatsApp] Starting connection...");
6959
+ const authPath = getAuthPath();
6960
+ if (!fs.existsSync(authPath)) {
6961
+ fs.mkdirSync(authPath, { recursive: true });
6962
+ }
6963
+ await debugLog(`[MagicMail WhatsApp] Auth path: ${authPath}`);
6964
+ const { state, saveCreds } = await baileys.useMultiFileAuthState(authPath);
6965
+ await debugLog("[MagicMail WhatsApp] Auth state loaded");
6966
+ const pino = require("pino");
6967
+ const logger2 = pino({ level: "silent" });
6968
+ await debugLog("[MagicMail WhatsApp] Creating WhatsApp socket...");
6969
+ const makeSocket = baileys.default || baileys.makeWASocket;
6970
+ const browserConfig = baileys.Browsers.ubuntu("Chrome");
6971
+ await debugLog(`[MagicMail WhatsApp] Browser config: ${JSON.stringify(browserConfig)}`);
6972
+ sock = makeSocket({
6973
+ auth: state,
6974
+ logger: logger2,
6975
+ browser: browserConfig,
6976
+ syncFullHistory: false,
6977
+ markOnlineOnConnect: false,
6978
+ generateHighQualityLinkPreview: false,
6979
+ getMessage: async (key) => {
6980
+ return { conversation: "" };
6981
+ }
6982
+ });
6983
+ await debugLog("[MagicMail WhatsApp] Socket created, registering event handlers...");
6984
+ let resolved = false;
6985
+ const resolveOnce = (result) => {
6986
+ if (!resolved) {
6987
+ resolved = true;
6988
+ resolve(result);
6989
+ }
6990
+ };
6991
+ setTimeout(() => {
6992
+ if (!resolved) {
6993
+ strapi2.log.warn("[MagicMail WhatsApp] Connection timeout - no QR or connection");
6994
+ resolveOnce({ success: true, status: connectionStatus, qrCode });
6995
+ }
6996
+ }, 3e4);
6997
+ sock.ev.on("connection.update", async (update) => {
6998
+ await debugLog(`[MagicMail WhatsApp] connection.update: ${JSON.stringify(update)}`);
6999
+ const { connection, lastDisconnect, qr } = update;
7000
+ if (qr) {
7001
+ await debugLog("[MagicMail WhatsApp] QR code received");
7002
+ try {
7003
+ const QRCode = require("qrcode");
7004
+ qrCode = await QRCode.toDataURL(qr);
7005
+ connectionStatus = "qr_pending";
7006
+ emit("qr", { qrCode });
7007
+ emit("status", { status: connectionStatus });
7008
+ strapi2.log.info("[MagicMail WhatsApp] [SUCCESS] QR Code generated - scan with WhatsApp");
7009
+ resolveOnce({ success: true, status: connectionStatus, qrCode });
7010
+ } catch (qrError) {
7011
+ strapi2.log.error("[MagicMail WhatsApp] QR generation error:", qrError.message);
7012
+ }
7013
+ }
7014
+ if (connection === "close") {
7015
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
7016
+ const isLoggedOut = statusCode === baileys.DisconnectReason.loggedOut;
7017
+ const isRestartRequired = statusCode === baileys.DisconnectReason.restartRequired;
7018
+ const isConnectionFailure = statusCode === 405;
7019
+ await debugLog(`[MagicMail WhatsApp] Connection closed - statusCode: ${statusCode}`);
7020
+ if (isLoggedOut) {
7021
+ connectionStatus = "disconnected";
7022
+ lastError = "Logged out from WhatsApp";
7023
+ qrCode = null;
7024
+ wasConnectedBefore = false;
7025
+ reconnectAttempts = 0;
7026
+ try {
7027
+ fs.rmSync(authPath, { recursive: true, force: true });
7028
+ } catch (e) {
7029
+ }
7030
+ strapi2.log.warn("[MagicMail WhatsApp] Logged out - auth cleared");
7031
+ } else if (isRestartRequired) {
7032
+ await debugLog("[MagicMail WhatsApp] Restart required - reconnecting...");
7033
+ connectionStatus = "connecting";
7034
+ setTimeout(() => {
7035
+ service.connect();
7036
+ }, 1e3);
7037
+ } else if (isConnectionFailure && reconnectAttempts < 2) {
7038
+ reconnectAttempts++;
7039
+ await debugLog(`[MagicMail WhatsApp] Connection rejected (405) - retrying (${reconnectAttempts}/2)`);
7040
+ try {
7041
+ fs.rmSync(authPath, { recursive: true, force: true });
7042
+ } catch (e) {
7043
+ }
7044
+ connectionStatus = "disconnected";
7045
+ qrCode = null;
7046
+ setTimeout(() => {
7047
+ service.connect();
7048
+ }, 3e3);
7049
+ } else if (isConnectionFailure) {
7050
+ connectionStatus = "disconnected";
7051
+ lastError = "WhatsApp connection rejected (405). Please try again later.";
7052
+ strapi2.log.error("[MagicMail WhatsApp] [ERROR] Connection rejected after retries.");
7053
+ resolveOnce({ success: false, status: connectionStatus, error: lastError });
7054
+ } else if (wasConnectedBefore && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
7055
+ reconnectAttempts++;
7056
+ connectionStatus = "connecting";
7057
+ await debugLog(`[MagicMail WhatsApp] Reconnecting (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`);
7058
+ setTimeout(() => {
7059
+ service.connect();
7060
+ }, 3e3 * reconnectAttempts);
7061
+ } else if (!wasConnectedBefore) {
7062
+ connectionStatus = "disconnected";
7063
+ qrCode = null;
7064
+ await debugLog("[MagicMail WhatsApp] Connection closed - waiting for QR scan");
7065
+ } else {
7066
+ connectionStatus = "disconnected";
7067
+ lastError = "Max reconnect attempts reached";
7068
+ strapi2.log.warn("[MagicMail WhatsApp] Max reconnect attempts reached");
7069
+ }
7070
+ emit("status", { status: connectionStatus, error: lastError });
7071
+ }
7072
+ if (connection === "open") {
7073
+ connectionStatus = "connected";
7074
+ qrCode = null;
7075
+ lastError = null;
7076
+ wasConnectedBefore = true;
7077
+ reconnectAttempts = 0;
7078
+ emit("status", { status: connectionStatus });
7079
+ strapi2.log.info("[MagicMail WhatsApp] [SUCCESS] Connected successfully!");
7080
+ resolveOnce({ success: true, status: connectionStatus });
7081
+ }
7082
+ });
7083
+ sock.ev.on("creds.update", saveCreds);
7084
+ } catch (error) {
7085
+ lastError = error.message;
7086
+ connectionStatus = "disconnected";
7087
+ strapi2.log.error("[MagicMail WhatsApp] Connection error:", error);
7088
+ resolve({ success: false, error: error.message });
7089
+ }
7090
+ });
7091
+ },
7092
+ /**
7093
+ * Disconnect WhatsApp and clear session
7094
+ * @returns {Promise<object>} Result with success status
7095
+ */
7096
+ async disconnect() {
7097
+ if (sock) {
7098
+ try {
7099
+ await sock.logout();
7100
+ } catch (e) {
7101
+ }
7102
+ sock = null;
7103
+ }
7104
+ connectionStatus = "disconnected";
7105
+ qrCode = null;
7106
+ emit("status", { status: connectionStatus });
7107
+ strapi2.log.info("[MagicMail WhatsApp] Disconnected");
7108
+ return { success: true };
7109
+ },
7110
+ /**
7111
+ * Send a text message to a phone number
7112
+ * @param {string} phoneNumber - Phone number with country code (e.g., "491234567890")
7113
+ * @param {string} message - Message text
7114
+ * @returns {Promise<object>} Result with success status
7115
+ */
7116
+ async sendMessage(phoneNumber, message) {
7117
+ if (connectionStatus !== "connected" || !sock) {
7118
+ return {
7119
+ success: false,
7120
+ error: "WhatsApp not connected. Please connect first."
7121
+ };
7122
+ }
7123
+ try {
7124
+ const formattedNumber = phoneNumber.replace(/[^\d]/g, "");
7125
+ const jid = `${formattedNumber}@s.whatsapp.net`;
7126
+ const [exists] = await sock.onWhatsApp(formattedNumber);
7127
+ if (!exists?.exists) {
7128
+ return {
7129
+ success: false,
7130
+ error: `Phone number ${phoneNumber} is not registered on WhatsApp`
7131
+ };
7132
+ }
7133
+ await sock.sendMessage(jid, { text: message });
7134
+ await debugLog(`[MagicMail WhatsApp] Message sent to ${formattedNumber}`);
7135
+ return { success: true, jid };
7136
+ } catch (error) {
7137
+ strapi2.log.error("[MagicMail WhatsApp] Send error:", error);
7138
+ return { success: false, error: error.message };
7139
+ }
7140
+ },
7141
+ /**
7142
+ * Send message using a template
7143
+ * @param {string} phoneNumber - Phone number
7144
+ * @param {string} templateName - Template identifier
7145
+ * @param {object} variables - Template variables to replace
7146
+ * @returns {Promise<object>} Result with success status
7147
+ */
7148
+ async sendTemplateMessage(phoneNumber, templateName, variables = {}) {
7149
+ try {
7150
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
7151
+ const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
7152
+ let template = templates[templateName];
7153
+ if (!template) {
7154
+ template = `*{{subject}}*
7155
+
7156
+ {{body}}`;
7157
+ }
7158
+ let message = template;
7159
+ for (const [key, value] of Object.entries(variables)) {
7160
+ message = message.replace(new RegExp(`{{${key}}}`, "g"), value);
7161
+ }
7162
+ return this.sendMessage(phoneNumber, message);
7163
+ } catch (error) {
7164
+ return { success: false, error: error.message };
7165
+ }
7166
+ },
7167
+ /**
7168
+ * Check if a phone number is on WhatsApp
7169
+ * @param {string} phoneNumber - Phone number to check
7170
+ * @returns {Promise<object>} Result with exists boolean
7171
+ */
7172
+ async checkNumber(phoneNumber) {
7173
+ if (connectionStatus !== "connected" || !sock) {
7174
+ return { success: false, error: "WhatsApp not connected" };
7175
+ }
7176
+ try {
7177
+ const formattedNumber = phoneNumber.replace(/[^\d]/g, "");
7178
+ const [result] = await sock.onWhatsApp(formattedNumber);
7179
+ return {
7180
+ success: true,
7181
+ exists: result?.exists || false,
7182
+ jid: result?.jid
7183
+ };
7184
+ } catch (error) {
7185
+ return { success: false, error: error.message };
7186
+ }
7187
+ },
7188
+ /**
7189
+ * Get session info
7190
+ * @returns {Promise<object|null>} Session info or null if not connected
7191
+ */
7192
+ async getSessionInfo() {
7193
+ if (connectionStatus !== "connected" || !sock) {
7194
+ return null;
7195
+ }
7196
+ try {
7197
+ const user = sock.user;
7198
+ return {
7199
+ phoneNumber: user?.id?.split(":")[0] || user?.id?.split("@")[0],
7200
+ name: user?.name,
7201
+ platform: "WhatsApp Web"
7202
+ };
7203
+ } catch (error) {
7204
+ return null;
7205
+ }
7206
+ },
7207
+ /**
7208
+ * Reset connection state (for manual cleanup)
7209
+ */
7210
+ reset() {
7211
+ sock = null;
7212
+ qrCode = null;
7213
+ connectionStatus = "disconnected";
7214
+ lastError = null;
7215
+ wasConnectedBefore = false;
7216
+ reconnectAttempts = 0;
7217
+ },
7218
+ /**
7219
+ * Save WhatsApp template
7220
+ * @param {string} templateName - Template identifier
7221
+ * @param {string} templateContent - Template content with {{variables}}
7222
+ * @returns {Promise<object>} Result with success status
7223
+ */
7224
+ async saveTemplate(templateName, templateContent) {
7225
+ try {
7226
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
7227
+ const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
7228
+ templates[templateName] = templateContent;
7229
+ await pluginStore.set({ key: "whatsapp_templates", value: templates });
7230
+ return { success: true };
7231
+ } catch (error) {
7232
+ return { success: false, error: error.message };
7233
+ }
7234
+ },
7235
+ /**
7236
+ * Get all WhatsApp templates
7237
+ * @returns {Promise<object>} Templates object
7238
+ */
7239
+ async getTemplates() {
7240
+ try {
7241
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
7242
+ const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
7243
+ return templates;
7244
+ } catch (error) {
7245
+ return {};
7246
+ }
7247
+ },
7248
+ /**
7249
+ * Delete a WhatsApp template
7250
+ * @param {string} templateName - Template identifier
7251
+ * @returns {Promise<object>} Result with success status
7252
+ */
7253
+ async deleteTemplate(templateName) {
7254
+ try {
7255
+ const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
7256
+ const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
7257
+ delete templates[templateName];
7258
+ await pluginStore.set({ key: "whatsapp_templates", value: templates });
7259
+ return { success: true };
7260
+ } catch (error) {
7261
+ return { success: false, error: error.message };
7262
+ }
7263
+ }
7264
+ };
7265
+ return service;
7266
+ };
7267
+ return whatsapp;
7268
+ }
6169
7269
  var services;
6170
7270
  var hasRequiredServices;
6171
7271
  function requireServices() {
@@ -6177,13 +7277,15 @@ function requireServices() {
6177
7277
  const licenseGuard2 = requireLicenseGuard();
6178
7278
  const emailDesigner2 = requireEmailDesigner();
6179
7279
  const analytics2 = requireAnalytics();
7280
+ const whatsapp2 = requireWhatsapp();
6180
7281
  services = {
6181
7282
  "email-router": emailRouter2,
6182
7283
  "account-manager": accountManager2,
6183
7284
  oauth: oauth2,
6184
7285
  "license-guard": licenseGuard2,
6185
7286
  "email-designer": emailDesigner2,
6186
- analytics: analytics2
7287
+ analytics: analytics2,
7288
+ whatsapp: whatsapp2
6187
7289
  };
6188
7290
  return services;
6189
7291
  }