strapi-plugin-magic-mail 1.0.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.
Files changed (91) hide show
  1. package/COPYRIGHT_NOTICE.txt +13 -0
  2. package/LICENSE +22 -0
  3. package/README.md +1420 -0
  4. package/admin/jsconfig.json +10 -0
  5. package/admin/src/components/AddAccountModal.jsx +1943 -0
  6. package/admin/src/components/Initializer.jsx +14 -0
  7. package/admin/src/components/LicenseGuard.jsx +475 -0
  8. package/admin/src/components/PluginIcon.jsx +5 -0
  9. package/admin/src/hooks/useAuthRefresh.js +44 -0
  10. package/admin/src/hooks/useLicense.js +158 -0
  11. package/admin/src/index.js +86 -0
  12. package/admin/src/pages/Analytics.jsx +762 -0
  13. package/admin/src/pages/App.jsx +111 -0
  14. package/admin/src/pages/EmailDesigner/EditorPage.jsx +1405 -0
  15. package/admin/src/pages/EmailDesigner/TemplateList.jsx +1807 -0
  16. package/admin/src/pages/HomePage.jsx +1233 -0
  17. package/admin/src/pages/LicensePage.jsx +424 -0
  18. package/admin/src/pages/RoutingRules.jsx +1141 -0
  19. package/admin/src/pages/Settings.jsx +603 -0
  20. package/admin/src/pluginId.js +3 -0
  21. package/admin/src/translations/de.json +71 -0
  22. package/admin/src/translations/en.json +70 -0
  23. package/admin/src/translations/es.json +71 -0
  24. package/admin/src/translations/fr.json +71 -0
  25. package/admin/src/translations/pt.json +71 -0
  26. package/admin/src/utils/fetchWithRetry.js +123 -0
  27. package/admin/src/utils/getTranslation.js +5 -0
  28. package/dist/_chunks/App-B-Gp4Vbr.js +7568 -0
  29. package/dist/_chunks/App-BymMjoGM.mjs +7543 -0
  30. package/dist/_chunks/LicensePage-Bl02myMx.mjs +342 -0
  31. package/dist/_chunks/LicensePage-CJXwPnEe.js +344 -0
  32. package/dist/_chunks/Settings-C_TmKwcz.mjs +400 -0
  33. package/dist/_chunks/Settings-zuFQ3pnn.js +402 -0
  34. package/dist/_chunks/de-CN-G9j1S.js +64 -0
  35. package/dist/_chunks/de-DS04rP54.mjs +64 -0
  36. package/dist/_chunks/en-BDc7Jk8u.js +64 -0
  37. package/dist/_chunks/en-BEFQJXvR.mjs +64 -0
  38. package/dist/_chunks/es-BpV1MIdm.js +64 -0
  39. package/dist/_chunks/es-DQHwzPpP.mjs +64 -0
  40. package/dist/_chunks/fr-BG1WfEVm.mjs +64 -0
  41. package/dist/_chunks/fr-vpziIpRp.js +64 -0
  42. package/dist/_chunks/pt-CMoGrOib.mjs +64 -0
  43. package/dist/_chunks/pt-ODpAhDNa.js +64 -0
  44. package/dist/admin/index.js +89 -0
  45. package/dist/admin/index.mjs +90 -0
  46. package/dist/server/index.js +6214 -0
  47. package/dist/server/index.mjs +6208 -0
  48. package/package.json +113 -0
  49. package/server/jsconfig.json +10 -0
  50. package/server/src/bootstrap.js +153 -0
  51. package/server/src/config/features.js +260 -0
  52. package/server/src/config/index.js +6 -0
  53. package/server/src/content-types/email-account/schema.json +93 -0
  54. package/server/src/content-types/email-event/index.js +8 -0
  55. package/server/src/content-types/email-event/schema.json +57 -0
  56. package/server/src/content-types/email-link/index.js +8 -0
  57. package/server/src/content-types/email-link/schema.json +49 -0
  58. package/server/src/content-types/email-log/index.js +8 -0
  59. package/server/src/content-types/email-log/schema.json +106 -0
  60. package/server/src/content-types/email-template/schema.json +74 -0
  61. package/server/src/content-types/email-template-version/schema.json +60 -0
  62. package/server/src/content-types/index.js +33 -0
  63. package/server/src/content-types/routing-rule/schema.json +59 -0
  64. package/server/src/controllers/accounts.js +220 -0
  65. package/server/src/controllers/analytics.js +347 -0
  66. package/server/src/controllers/controller.js +26 -0
  67. package/server/src/controllers/email-designer.js +474 -0
  68. package/server/src/controllers/index.js +21 -0
  69. package/server/src/controllers/license.js +267 -0
  70. package/server/src/controllers/oauth.js +474 -0
  71. package/server/src/controllers/routing-rules.js +122 -0
  72. package/server/src/controllers/test.js +383 -0
  73. package/server/src/destroy.js +23 -0
  74. package/server/src/index.js +25 -0
  75. package/server/src/middlewares/index.js +3 -0
  76. package/server/src/policies/index.js +3 -0
  77. package/server/src/register.js +5 -0
  78. package/server/src/routes/admin.js +469 -0
  79. package/server/src/routes/content-api.js +37 -0
  80. package/server/src/routes/index.js +9 -0
  81. package/server/src/services/account-manager.js +277 -0
  82. package/server/src/services/analytics.js +496 -0
  83. package/server/src/services/email-designer.js +870 -0
  84. package/server/src/services/email-router.js +1420 -0
  85. package/server/src/services/index.js +17 -0
  86. package/server/src/services/license-guard.js +418 -0
  87. package/server/src/services/oauth.js +515 -0
  88. package/server/src/services/service.js +7 -0
  89. package/server/src/utils/encryption.js +81 -0
  90. package/strapi-admin.js +4 -0
  91. package/strapi-server.js +4 -0
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "version": "1.0.1",
3
+ "keywords": [
4
+ "strapi",
5
+ "strapi-plugin",
6
+ "strapi-v5",
7
+ "email",
8
+ "multi-account",
9
+ "smtp",
10
+ "nodemailer",
11
+ "email-routing",
12
+ "email-analytics"
13
+ ],
14
+ "type": "commonjs",
15
+ "exports": {
16
+ "./package.json": "./package.json",
17
+ "./strapi-admin": {
18
+ "source": "./admin/src/index.js",
19
+ "import": "./dist/admin/index.mjs",
20
+ "require": "./dist/admin/index.js",
21
+ "default": "./dist/admin/index.js"
22
+ },
23
+ "./strapi-server": {
24
+ "source": "./server/src/index.js",
25
+ "import": "./dist/server/index.mjs",
26
+ "require": "./dist/server/index.js",
27
+ "default": "./dist/server/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "server",
33
+ "admin",
34
+ "strapi-admin.js",
35
+ "strapi-server.js",
36
+ "README.md",
37
+ "LICENSE",
38
+ "COPYRIGHT_NOTICE.txt",
39
+ "pics"
40
+ ],
41
+ "scripts": {
42
+ "build": "strapi-plugin build",
43
+ "watch": "strapi-plugin watch",
44
+ "watch:link": "strapi-plugin watch:link",
45
+ "verify": "strapi-plugin verify",
46
+ "test": "node test-magic-mail.js --quick",
47
+ "test:full": "node test-magic-mail.js --full"
48
+ },
49
+ "dependencies": {
50
+ "@heroicons/react": "^2.2.0",
51
+ "@strapi/design-system": "^2.0.0-rc.30",
52
+ "@strapi/icons": "^2.0.0-rc.30",
53
+ "decode-html": "^2.0.0",
54
+ "form-data": "^4.0.4",
55
+ "html-to-text": "^9.0.5",
56
+ "mustache": "^4.2.0",
57
+ "nodemailer": "^7.0.10",
58
+ "react-email-editor": "^1.7.11",
59
+ "react-intl": "^7.1.14",
60
+ "striptags": "^3.2.0",
61
+ "yup": "^1.7.1"
62
+ },
63
+ "devDependencies": {
64
+ "@semantic-release/changelog": "^6.0.3",
65
+ "@semantic-release/commit-analyzer": "^13.0.0",
66
+ "@semantic-release/git": "^10.0.1",
67
+ "@semantic-release/github": "^11.0.2",
68
+ "@semantic-release/npm": "^13.1.2",
69
+ "@semantic-release/release-notes-generator": "^14.0.1",
70
+ "@strapi/sdk-plugin": "^5.3.2",
71
+ "@strapi/strapi": "^5.30.1",
72
+ "prettier": "^3.6.2",
73
+ "react": "^18.3.1",
74
+ "react-dom": "^18.3.1",
75
+ "react-router-dom": "^6.30.1",
76
+ "semantic-release": "^24.2.3",
77
+ "styled-components": "^6.1.19"
78
+ },
79
+ "peerDependencies": {
80
+ "@strapi/sdk-plugin": "^5.3.2",
81
+ "@strapi/strapi": "^5.30.1",
82
+ "react": "^18.3.1",
83
+ "react-dom": "^18.3.1",
84
+ "react-router-dom": "^6.30.1",
85
+ "styled-components": "^6.1.19"
86
+ },
87
+ "overrides": {
88
+ "glob": "^13.0.0",
89
+ "esbuild": "^0.27.0",
90
+ "tmp": "^0.2.3",
91
+ "vite": "^6.1.7",
92
+ "koa": "^2.16.1"
93
+ },
94
+ "strapi": {
95
+ "kind": "plugin",
96
+ "name": "magic-mail",
97
+ "displayName": "MagicMail - Email Business Suite",
98
+ "description": "Multi-account email management with smart routing, failover, and analytics for Strapi v5"
99
+ },
100
+ "name": "strapi-plugin-magic-mail",
101
+ "description": "Multi-account email management with smart routing, failover, and analytics for Strapi v5",
102
+ "license": "MIT",
103
+ "author": "Schero D.",
104
+ "repository": {
105
+ "type": "git",
106
+ "url": "https://github.com/Schero94/Magic-Mail.git"
107
+ },
108
+ "bugs": {
109
+ "url": "https://github.com/Schero94/Magic-Mail/issues"
110
+ },
111
+ "homepage": "https://github.com/Schero94/Magic-Mail#readme",
112
+ "private": false
113
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "commonjs",
5
+ "allowSyntheticDefaultImports": true,
6
+ "esModuleInterop": true
7
+ },
8
+ "include": ["./src/**/*.js"],
9
+ "exclude": ["node_modules"]
10
+ }
@@ -0,0 +1,153 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Bootstrap: Initialize MagicMail plugin
5
+ * Sets up email account counter resets and health checks
6
+ * OVERRIDES Strapi's native email service with MagicMail router
7
+ */
8
+
9
+ module.exports = async ({ strapi }) => {
10
+ strapi.log.info('🚀 [magic-mail] Bootstrap starting...');
11
+
12
+ try {
13
+ // Initialize License Guard
14
+ const licenseGuardService = strapi.plugin('magic-mail').service('license-guard');
15
+
16
+ // Wait a bit for all services to be ready
17
+ setTimeout(async () => {
18
+ const licenseStatus = await licenseGuardService.initialize();
19
+
20
+ if (!licenseStatus.valid && licenseStatus.demo) {
21
+ strapi.log.error('╔════════════════════════════════════════════════════════════════╗');
22
+ strapi.log.error('║ ❌ MAGICMAIL - NO VALID LICENSE ║');
23
+ strapi.log.error('║ ║');
24
+ strapi.log.error('║ This plugin requires a valid license to operate. ║');
25
+ strapi.log.error('║ Please activate your license via Admin UI: ║');
26
+ strapi.log.error('║ Go to MagicMail → License tab ║');
27
+ strapi.log.error('║ ║');
28
+ strapi.log.error('║ Click "Generate Free License" to get started! ║');
29
+ strapi.log.error('╚════════════════════════════════════════════════════════════════╝');
30
+ } else if (licenseStatus.gracePeriod) {
31
+ strapi.log.warn('⚠️ Running on grace period (license server unreachable)');
32
+ }
33
+ // No additional log here, as initialize() already outputs the license box
34
+ }, 2000);
35
+
36
+ const accountManager = strapi.plugin('magic-mail').service('account-manager');
37
+ const emailRouter = strapi.plugin('magic-mail').service('email-router');
38
+
39
+ // ============================================================
40
+ // OVERRIDE STRAPI'S NATIVE EMAIL SERVICE
41
+ // ============================================================
42
+
43
+ // Try to get email service (support both v4 and v5 APIs)
44
+ const originalEmailService = strapi.plugin('email')?.service?.('email') ||
45
+ strapi.plugins?.email?.services?.email;
46
+
47
+ if (originalEmailService && originalEmailService.send) {
48
+ const originalSend = originalEmailService.send.bind(originalEmailService);
49
+
50
+ // Override the send method
51
+ originalEmailService.send = async (emailData) => {
52
+ strapi.log.info('[magic-mail] 📧 Email intercepted from native Strapi service');
53
+ strapi.log.debug('[magic-mail] Email data:', {
54
+ to: emailData.to,
55
+ subject: emailData.subject,
56
+ templateId: emailData.templateId,
57
+ hasHtml: !!emailData.html,
58
+ hasText: !!emailData.text,
59
+ });
60
+
61
+ try {
62
+ // Map 'data' to 'templateData' for backward compatibility
63
+ if (emailData.data && !emailData.templateData) {
64
+ emailData.templateData = emailData.data;
65
+ }
66
+
67
+ // Route through MagicMail
68
+ const result = await emailRouter.send(emailData);
69
+
70
+ strapi.log.info('[magic-mail] ✅ Email routed successfully through MagicMail');
71
+ return result;
72
+ } catch (magicMailError) {
73
+ strapi.log.warn('[magic-mail] ⚠️ MagicMail routing failed, falling back to original service');
74
+ strapi.log.error('[magic-mail] Error:', magicMailError.message);
75
+
76
+ // Fallback to original Strapi email service
77
+ return await originalSend(emailData);
78
+ }
79
+ };
80
+
81
+ strapi.log.info('[magic-mail] ✅ Native email service overridden!');
82
+ strapi.log.info('[magic-mail] 💡 All strapi.plugins.email.services.email.send() calls will route through MagicMail');
83
+ } else {
84
+ strapi.log.warn('[magic-mail] ⚠️ Native email service not found - MagicMail will work standalone');
85
+ strapi.log.warn('[magic-mail] 💡 Make sure @strapi/plugin-email is installed');
86
+ }
87
+
88
+ // ============================================================
89
+ // COUNTER RESET SCHEDULES
90
+ // ============================================================
91
+
92
+ // Reset hourly counters every hour
93
+ const hourlyResetInterval = setInterval(async () => {
94
+ try {
95
+ if (!strapi || !strapi.plugin) {
96
+ console.warn('[magic-mail] Strapi not available for hourly reset');
97
+ return;
98
+ }
99
+ const accountMgr = strapi.plugin('magic-mail').service('account-manager');
100
+ await accountMgr.resetCounters('hourly');
101
+ strapi.log.info('[magic-mail] ✅ Hourly counters reset');
102
+ } catch (err) {
103
+ console.error('[magic-mail] Hourly reset error:', err.message);
104
+ }
105
+ }, 60 * 60 * 1000); // Every hour
106
+
107
+ // Store interval for cleanup
108
+ if (!global.magicMailIntervals) global.magicMailIntervals = {};
109
+ global.magicMailIntervals.hourly = hourlyResetInterval;
110
+
111
+ // Reset daily counters at midnight
112
+ const now = new Date();
113
+ const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0);
114
+ const msUntilMidnight = midnight - now;
115
+
116
+ setTimeout(async () => {
117
+ try {
118
+ if (!strapi || !strapi.plugin) {
119
+ console.warn('[magic-mail] Strapi not available for daily reset');
120
+ return;
121
+ }
122
+ const accountMgr = strapi.plugin('magic-mail').service('account-manager');
123
+ await accountMgr.resetCounters('daily');
124
+ strapi.log.info('[magic-mail] ✅ Daily counters reset');
125
+
126
+ // Then set daily interval
127
+ const dailyResetInterval = setInterval(async () => {
128
+ try {
129
+ if (!strapi || !strapi.plugin) {
130
+ console.warn('[magic-mail] Strapi not available for daily reset');
131
+ return;
132
+ }
133
+ const accountMgr = strapi.plugin('magic-mail').service('account-manager');
134
+ await accountMgr.resetCounters('daily');
135
+ strapi.log.info('[magic-mail] ✅ Daily counters reset');
136
+ } catch (err) {
137
+ console.error('[magic-mail] Daily reset error:', err.message);
138
+ }
139
+ }, 24 * 60 * 60 * 1000); // Every 24 hours
140
+
141
+ // Store interval for cleanup
142
+ global.magicMailIntervals.daily = dailyResetInterval;
143
+ } catch (err) {
144
+ console.error('[magic-mail] Initial daily reset error:', err.message);
145
+ }
146
+ }, msUntilMidnight);
147
+
148
+ strapi.log.info('[magic-mail] ✅ Counter reset schedules initialized');
149
+ strapi.log.info('[magic-mail] ✅ Bootstrap complete');
150
+ } catch (err) {
151
+ strapi.log.error('[magic-mail] ❌ Bootstrap error:', err);
152
+ }
153
+ };
@@ -0,0 +1,260 @@
1
+ /**
2
+ * MagicMail Feature Definitions
3
+ * Defines which features are available for each license tier
4
+ */
5
+
6
+ module.exports = {
7
+ // FREE/DEMO Features
8
+ free: {
9
+ maxAccounts: 3, // 3 Accounts (can be OAuth!)
10
+ maxRoutingRules: 5,
11
+ maxEmailTemplates: 25, // 25 Templates - Genug zum Testen & kleine Projekte!
12
+ providers: ['smtp', 'gmail-oauth', 'microsoft-oauth', 'yahoo-oauth'], // Alle Provider erlaubt!
13
+ features: [
14
+ 'basic-smtp',
15
+ 'oauth-gmail',
16
+ 'oauth-microsoft',
17
+ 'oauth-yahoo',
18
+ 'basic-routing',
19
+ 'email-logging',
20
+ 'account-testing',
21
+ 'strapi-service-override',
22
+ 'email-designer-basic', // Basic Email Designer
23
+ 'email-designer-import-export', // Import/Export auch in Free! (Community-freundlich)
24
+ ],
25
+ },
26
+
27
+ // PREMIUM Features
28
+ premium: {
29
+ maxAccounts: 10, // 10 Accounts - Perfekt für kleine Teams
30
+ maxRoutingRules: 20,
31
+ maxEmailTemplates: 100, // 100 Templates - Mehr als genug für die meisten Projekte
32
+ providers: ['smtp', 'gmail-oauth', 'microsoft-oauth', 'yahoo-oauth'],
33
+ features: [
34
+ 'basic-smtp',
35
+ 'basic-routing',
36
+ 'email-logging',
37
+ 'oauth-gmail',
38
+ 'oauth-microsoft',
39
+ 'oauth-yahoo',
40
+ 'account-testing',
41
+ 'strapi-service-override',
42
+ 'email-designer-basic',
43
+ 'email-designer-templates',
44
+ 'email-designer-import-export',
45
+ 'email-designer-versioning', // NEU in Premium: Versionierung!
46
+ 'analytics-basic', // Basic Analytics
47
+ ],
48
+ },
49
+
50
+ // ADVANCED Features
51
+ advanced: {
52
+ maxAccounts: -1, // Unlimited
53
+ maxRoutingRules: -1, // Unlimited
54
+ maxEmailTemplates: 500, // 500 Templates - Für größere Projekte
55
+ providers: ['smtp', 'gmail-oauth', 'microsoft-oauth', 'yahoo-oauth', 'sendgrid', 'mailgun'],
56
+ features: [
57
+ 'basic-smtp',
58
+ 'basic-routing',
59
+ 'email-logging',
60
+ 'oauth-gmail',
61
+ 'oauth-microsoft',
62
+ 'oauth-yahoo',
63
+ 'sendgrid',
64
+ 'mailgun',
65
+ 'dkim-signing',
66
+ 'priority-headers',
67
+ 'list-unsubscribe',
68
+ 'security-validation',
69
+ 'analytics-dashboard',
70
+ 'advanced-routing',
71
+ 'account-testing',
72
+ 'strapi-service-override',
73
+ 'email-designer-basic', // NEW
74
+ 'email-designer-templates', // NEW
75
+ 'email-designer-versioning', // NEW: Template Versioning
76
+ 'email-designer-import-export', // NEW: Import/Export
77
+ ],
78
+ },
79
+
80
+ // ENTERPRISE Features
81
+ enterprise: {
82
+ maxAccounts: -1, // Unlimited
83
+ maxRoutingRules: -1, // Unlimited
84
+ maxEmailTemplates: -1, // Unlimited Templates - Keine Limits!
85
+ providers: ['smtp', 'gmail-oauth', 'microsoft-oauth', 'yahoo-oauth', 'sendgrid', 'mailgun'],
86
+ features: [
87
+ 'basic-smtp',
88
+ 'basic-routing',
89
+ 'email-logging',
90
+ 'oauth-gmail',
91
+ 'oauth-microsoft',
92
+ 'oauth-yahoo',
93
+ 'sendgrid',
94
+ 'mailgun',
95
+ 'dkim-signing',
96
+ 'priority-headers',
97
+ 'list-unsubscribe',
98
+ 'security-validation',
99
+ 'analytics-dashboard',
100
+ 'advanced-routing',
101
+ 'multi-tenant',
102
+ 'compliance-reports',
103
+ 'custom-security-rules',
104
+ 'priority-support',
105
+ 'account-testing',
106
+ 'strapi-service-override',
107
+ 'email-designer-basic', // NEW
108
+ 'email-designer-templates', // NEW
109
+ 'email-designer-versioning', // NEW
110
+ 'email-designer-import-export', // NEW
111
+ 'email-designer-custom-blocks', // NEW: Custom Blocks
112
+ 'email-designer-team-library', // NEW: Team Library
113
+ 'email-designer-a-b-testing', // NEW: A/B Testing
114
+ ],
115
+ },
116
+
117
+ /**
118
+ * Check if a feature is available for given license tier
119
+ */
120
+ hasFeature(licenseData, featureName) {
121
+ /** console.log(`[features.js] 🔍 hasFeature called with:`, {
122
+ featureName,
123
+ hasLicenseData: !!licenseData,
124
+ licenseDataKeys: licenseData ? Object.keys(licenseData) : [],
125
+ featuresObject: licenseData?.features,
126
+ tier: licenseData?.tier,
127
+ // Show actual feature flag values
128
+ featureAdvanced: licenseData?.featureAdvanced,
129
+ featureEnterprise: licenseData?.featureEnterprise,
130
+ featurePremium: licenseData?.featurePremium,
131
+ });
132
+ */
133
+ if (!licenseData) {
134
+ console.log(`[features.js] ⚠️ No license data → using FREE tier`);
135
+ // Demo mode - only free features
136
+ return this.free.features.includes(featureName);
137
+ }
138
+
139
+ // Determine tier: check multiple possible formats
140
+ let isEnterprise = false;
141
+ let isAdvanced = false;
142
+ let isPremium = false;
143
+
144
+ // Check tier field directly
145
+ if (licenseData.tier) {
146
+ isEnterprise = licenseData.tier === 'enterprise';
147
+ isAdvanced = licenseData.tier === 'advanced';
148
+ isPremium = licenseData.tier === 'premium';
149
+ }
150
+
151
+ // Check features object (for backward compatibility)
152
+ if (licenseData.features) {
153
+ isEnterprise = isEnterprise || licenseData.features.enterprise === true;
154
+ isAdvanced = isAdvanced || licenseData.features.advanced === true;
155
+ isPremium = isPremium || licenseData.features.premium === true;
156
+ }
157
+
158
+ // Check feature flags directly on licenseData (magicapi format)
159
+ // These are boolean fields from the API
160
+ if (licenseData.featureEnterprise === true) {
161
+ isEnterprise = true;
162
+ }
163
+ if (licenseData.featureAdvanced === true) {
164
+ isAdvanced = true;
165
+ }
166
+ if (licenseData.featurePremium === true) {
167
+ isPremium = true;
168
+ }
169
+
170
+ /* console.log(`[features.js] 🔍 Tier detection:`, {
171
+ isEnterprise,
172
+ isAdvanced,
173
+ isPremium,
174
+ tierField: licenseData.tier,
175
+ featureAdvanced: licenseData.featureAdvanced,
176
+ featureEnterprise: licenseData.featureEnterprise,
177
+ featurePremium: licenseData.featurePremium,
178
+ featuresAdvanced: licenseData.features?.advanced,
179
+ featuresEnterprise: licenseData.features?.enterprise,
180
+ featuresPremium: licenseData.features?.premium,
181
+ });
182
+ */
183
+ // Check tiers in order: enterprise > advanced > premium > free
184
+ if (isEnterprise && this.enterprise.features.includes(featureName)) {
185
+ console.log(`[features.js] ✅ ENTERPRISE tier has feature "${featureName}"`);
186
+ return true;
187
+ }
188
+ if (isAdvanced && this.advanced.features.includes(featureName)) {
189
+ console.log(`[features.js] ✅ ADVANCED tier has feature "${featureName}"`);
190
+ return true;
191
+ }
192
+ if (isPremium && this.premium.features.includes(featureName)) {
193
+ console.log(`[features.js] ✅ PREMIUM tier has feature "${featureName}"`);
194
+ return true;
195
+ }
196
+
197
+ const inFree = this.free.features.includes(featureName);
198
+ console.log(`[features.js] ${inFree ? '✅' : '❌'} FREE tier check for "${featureName}": ${inFree}`);
199
+ return inFree;
200
+ },
201
+
202
+ /**
203
+ * Get max allowed accounts for license tier
204
+ */
205
+ getMaxAccounts(licenseData) {
206
+ if (!licenseData) return this.free.maxAccounts;
207
+
208
+ // Check both formats: features object and direct feature flags
209
+ if (licenseData.featureEnterprise === true || licenseData.features?.enterprise === true) return this.enterprise.maxAccounts;
210
+ if (licenseData.featureAdvanced === true || licenseData.features?.advanced === true) return this.advanced.maxAccounts;
211
+ if (licenseData.featurePremium === true || licenseData.features?.premium === true) return this.premium.maxAccounts;
212
+
213
+ return this.free.maxAccounts;
214
+ },
215
+
216
+ /**
217
+ * Get max allowed routing rules for license tier
218
+ */
219
+ getMaxRoutingRules(licenseData) {
220
+ if (!licenseData) return this.free.maxRoutingRules;
221
+
222
+ // Check both formats: features object and direct feature flags
223
+ if (licenseData.featureEnterprise === true || licenseData.features?.enterprise === true) return this.enterprise.maxRoutingRules;
224
+ if (licenseData.featureAdvanced === true || licenseData.features?.advanced === true) return this.advanced.maxRoutingRules;
225
+ if (licenseData.featurePremium === true || licenseData.features?.premium === true) return this.premium.maxRoutingRules;
226
+
227
+ return this.free.maxRoutingRules;
228
+ },
229
+
230
+ /**
231
+ * Check if provider is allowed for license tier
232
+ */
233
+ isProviderAllowed(licenseData, provider) {
234
+ if (!licenseData) {
235
+ return this.free.providers.includes(provider);
236
+ }
237
+
238
+ // Check both formats: features object and direct feature flags
239
+ if (licenseData.featureEnterprise === true || licenseData.features?.enterprise === true) return this.enterprise.providers.includes(provider);
240
+ if (licenseData.featureAdvanced === true || licenseData.features?.advanced === true) return this.advanced.providers.includes(provider);
241
+ if (licenseData.featurePremium === true || licenseData.features?.premium === true) return this.premium.providers.includes(provider);
242
+
243
+ return this.free.providers.includes(provider);
244
+ },
245
+
246
+ /**
247
+ * Get max allowed email templates for license tier
248
+ */
249
+ getMaxEmailTemplates(licenseData) {
250
+ if (!licenseData) return this.free.maxEmailTemplates;
251
+
252
+ // Check both formats: features object and direct feature flags
253
+ if (licenseData.featureEnterprise === true || licenseData.features?.enterprise === true) return this.enterprise.maxEmailTemplates;
254
+ if (licenseData.featureAdvanced === true || licenseData.features?.advanced === true) return this.advanced.maxEmailTemplates;
255
+ if (licenseData.featurePremium === true || licenseData.features?.premium === true) return this.premium.maxEmailTemplates;
256
+
257
+ return this.free.maxEmailTemplates;
258
+ },
259
+ };
260
+
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ default: {},
5
+ validator() {},
6
+ };
@@ -0,0 +1,93 @@
1
+ {
2
+ "kind": "collectionType",
3
+ "collectionName": "magic_mail_accounts",
4
+ "info": {
5
+ "singularName": "email-account",
6
+ "pluralName": "email-accounts",
7
+ "displayName": "Email Account",
8
+ "description": "Email provider accounts for multi-account email sending"
9
+ },
10
+ "options": {
11
+ "draftAndPublish": false
12
+ },
13
+ "pluginOptions": {
14
+ "content-manager": {
15
+ "visible": false
16
+ },
17
+ "content-type-builder": {
18
+ "visible": false
19
+ }
20
+ },
21
+ "attributes": {
22
+ "name": {
23
+ "type": "string",
24
+ "required": true,
25
+ "unique": true
26
+ },
27
+ "description": {
28
+ "type": "text"
29
+ },
30
+ "provider": {
31
+ "type": "enumeration",
32
+ "enum": ["smtp", "gmail-oauth", "microsoft-oauth", "yahoo-oauth", "sendgrid", "mailgun", "ses", "nodemailer"],
33
+ "required": true,
34
+ "default": "smtp"
35
+ },
36
+ "config": {
37
+ "type": "json",
38
+ "required": true
39
+ },
40
+ "oauth": {
41
+ "type": "json"
42
+ },
43
+ "fromEmail": {
44
+ "type": "email",
45
+ "required": true
46
+ },
47
+ "fromName": {
48
+ "type": "string"
49
+ },
50
+ "replyTo": {
51
+ "type": "email"
52
+ },
53
+ "isActive": {
54
+ "type": "boolean",
55
+ "default": true,
56
+ "required": true
57
+ },
58
+ "isPrimary": {
59
+ "type": "boolean",
60
+ "default": false
61
+ },
62
+ "priority": {
63
+ "type": "integer",
64
+ "default": 1,
65
+ "min": 1,
66
+ "max": 10
67
+ },
68
+ "dailyLimit": {
69
+ "type": "integer",
70
+ "default": 0
71
+ },
72
+ "hourlyLimit": {
73
+ "type": "integer",
74
+ "default": 0
75
+ },
76
+ "emailsSentToday": {
77
+ "type": "integer",
78
+ "default": 0
79
+ },
80
+ "emailsSentThisHour": {
81
+ "type": "integer",
82
+ "default": 0
83
+ },
84
+ "totalEmailsSent": {
85
+ "type": "integer",
86
+ "default": 0
87
+ },
88
+ "lastUsed": {
89
+ "type": "datetime"
90
+ }
91
+ }
92
+ }
93
+
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const schema = require('./schema.json');
4
+
5
+ module.exports = {
6
+ schema,
7
+ };
8
+