@technomoron/mail-magic 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGES CHANGED
@@ -1,4 +1,10 @@
1
- CHANGES
1
+ Version 1.0.5 (2025-10-29)
2
+
3
+ - Store compiled templates and assets under domain-rooted directories inside
4
+ `CONFIG_PATH`, removing the redundant user segment while keeping slugs and
5
+ API behaviour unchanged.
6
+ - Update documentation and examples to reflect the domain-first configuration
7
+ layout.
2
8
 
3
9
  Version 1.0.4 (2025-09-27)
4
10
 
@@ -22,4 +28,3 @@ Version 1.0.3 (2025-09-26)
22
28
  to match the renamed package and repository.
23
29
  - Added workspace metadata and sample templates so pnpm and new installs pick up example
24
30
  assets.
25
-
package/README.md CHANGED
@@ -24,7 +24,7 @@ During development you can run `npm run dev` for a watch mode that recompiles on
24
24
  ## Configuration
25
25
 
26
26
  - **Environment variables** are defined in `src/store/envloader.ts`. Important settings include SMTP credentials, API host/port, the config directory path, and database options.
27
- - **Config directory** (`CONFIG_PATH`) contains JSON seed data (`init-data.json`), optional API key files, and template assets. Review `config-example/` for the recommended layout, in particular the `form-template/` and `tx-template/` folders used for compiled Nunjucks templates.
27
+ - **Config directory** (`CONFIG_PATH`) contains JSON seed data (`init-data.json`), optional API key files, and template assets. Each domain now lives directly under the config root (for example `config/example.com/form-template/…`). Review `config-example/` for the recommended layout, in particular the `form-template/` and `tx-template/` folders used for compiled Nunjucks templates.
28
28
  - **Database** defaults to SQLite (`maildata.db`). You can switch dialects by updating the environment options if your deployment requires another database.
29
29
 
30
30
  When `DB_AUTO_RELOAD` is enabled the service watches `init-data.json` and refreshes templates and forms without a restart.
package/TUTORIAL.MD ADDED
@@ -0,0 +1,291 @@
1
+ # Mail Magic Configuration Tutorial (MyOrg)
2
+
3
+ This guide walks through building a standalone configuration tree for the user `myorg` and the domain `myorg.com`. The finished layout adds a contact form template and a transactional welcome template that both reuse partials and embed the MyOrg logo inline so it is shipped as a CID attachment.
4
+
5
+ ---
6
+
7
+ ## 1. Prepare an external config workspace
8
+
9
+ Mail Magic loads configuration from the folder referenced by the `CONFIG_PATH` environment variable. Keeping your custom assets outside the application repository makes upgrades easier.
10
+
11
+ ```bash
12
+ # run this next to the mail-magic checkout
13
+ mkdir -p ../myorg-config
14
+ export CONFIG_ROOT=$(realpath ../myorg-config)
15
+ ```
16
+
17
+ Update your `.env` (or runtime environment) to point at the new workspace:
18
+
19
+ ```
20
+ CONFIG_PATH=${CONFIG_ROOT}
21
+ DB_AUTO_RELOAD=1 # optional: hot‑reload init-data and templates
22
+ ```
23
+
24
+ From now on the tutorial assumes `${CONFIG_ROOT}` is the root of the custom config tree.
25
+
26
+ ---
27
+
28
+ ## 2. Create the base directory structure
29
+
30
+ ```bash
31
+ mkdir -p \
32
+ "$CONFIG_ROOT"/myorg.com/{form-template,tx-template} \
33
+ "$CONFIG_ROOT"/myorg.com/form-template/{partials,assets} \
34
+ "$CONFIG_ROOT"/myorg.com/tx-template/{partials,assets}
35
+ ```
36
+
37
+ The resulting tree should look like this (logo placement shown for clarity — add the file in step 4):
38
+
39
+ ```
40
+ myorg-config/
41
+ ├── init-data.json
42
+ └── myorg.com/
43
+ ├── form-template/
44
+ │ ├── assets/
45
+ │ ├── contact.njk
46
+ │ └── partials/
47
+ │ ├── footer.njk
48
+ │ └── header.njk
49
+ └── tx-template/
50
+ ├── assets/
51
+ │ └── logo.png
52
+ ├── partials/
53
+ │ ├── footer.njk
54
+ │ └── header.njk
55
+ └── welcome.njk
56
+ ```
57
+
58
+ > **Tip:** If you want to share partials between templates, keep file names aligned (e.g. identical `header.njk` content under both `form-template/partials/` and `tx-template/partials/`).
59
+
60
+ ---
61
+
62
+ ## 3. Seed users, domains, and templates with `init-data.json`
63
+
64
+ Create `${CONFIG_ROOT}/init-data.json` so the service can bootstrap the MyOrg user, domain, and template metadata:
65
+
66
+ ```json
67
+ {
68
+ "user": [
69
+ {
70
+ "user_id": 10,
71
+ "idname": "myorg",
72
+ "token": "<generate-a-32-char-hex-token>",
73
+ "name": "MyOrg",
74
+ "email": "notifications@myorg.com"
75
+ }
76
+ ],
77
+ "domain": [
78
+ {
79
+ "domain_id": 10,
80
+ "user_id": 10,
81
+ "name": "myorg.com",
82
+ "sender": "MyOrg Mailer <noreply@myorg.com>"
83
+ }
84
+ ],
85
+ "template": [
86
+ {
87
+ "template_id": 100,
88
+ "user_id": 10,
89
+ "domain_id": 10,
90
+ "name": "welcome",
91
+ "slug": "welcome",
92
+ "locale": "",
93
+ "filename": "welcome.njk",
94
+ "sender": "support@myorg.com",
95
+ "subject": "Welcome to MyOrg",
96
+ "template": ""
97
+ }
98
+ ],
99
+ "form": [
100
+ {
101
+ "form_id": 100,
102
+ "user_id": 10,
103
+ "domain_id": 10,
104
+ "idname": "contact",
105
+ "filename": "contact.njk",
106
+ "sender": "MyOrg Support <support@myorg.com>",
107
+ "recipient": "contact@myorg.com",
108
+ "subject": "New contact form submission"
109
+ }
110
+ ]
111
+ }
112
+ ```
113
+
114
+ - Generate the API token with `openssl rand -hex 16` (or any 32-character hex string).
115
+ - Leave `template` empty; Mail Magic will populate it with the flattened HTML the first time it processes the files.
116
+ - Set `DB_AUTO_RELOAD=1` (see step 1) if you want the service to re-import whenever `init-data.json` changes.
117
+
118
+ ---
119
+
120
+ ## 4. Author shared partials and templates
121
+
122
+ ### 4.1 Transactional email partials (`tx-template/partials`)
123
+
124
+ `$CONFIG_ROOT/myorg.com/tx-template/partials/header.njk`
125
+
126
+ ```njk
127
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#102347;color:#ffffff;padding:24px 0;font-family:'Segoe UI',Tahoma,sans-serif;">
128
+ <tr>
129
+ <td align="center">
130
+ <img src="asset('logo.png', true)" alt="MyOrg" width="96" height="96" style="display:block;border:none;border-radius:50%;box-shadow:0 4px 14px rgba(0,0,0,0.18);" />
131
+ <h1 style="margin:16px 0 0;font-size:24px;letter-spacing:0.08em;text-transform:uppercase;">MyOrg</h1>
132
+ </td>
133
+ </tr>
134
+ </table>
135
+ ```
136
+
137
+ `$CONFIG_ROOT/myorg.com/tx-template/partials/footer.njk`
138
+
139
+ ```njk
140
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f4f6fb;color:#6b7280;padding:24px 0;font-family:'Segoe UI',Tahoma,sans-serif;font-size:12px;">
141
+ <tr>
142
+ <td align="center">
143
+ <p style="margin:0;">You are receiving this email because you created a MyOrg account.</p>
144
+ <p style="margin:8px 0 0;">MyOrg, 123 Demo Street, Oslo</p>
145
+ </td>
146
+ </tr>
147
+ </table>
148
+ ```
149
+
150
+ ### 4.2 Transactional welcome template (`tx-template/welcome.njk`)
151
+
152
+ `$CONFIG_ROOT/myorg.com/tx-template/welcome.njk`
153
+
154
+ ```njk
155
+ {% include 'partials/header.njk' %}
156
+
157
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#ffffff;padding:32px 0;font-family:'Segoe UI',Tahoma,sans-serif;color:#1f2937;">
158
+ <tr>
159
+ <td align="center">
160
+ <table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:92%;text-align:left;">
161
+ <tr>
162
+ <td style="padding:0 0 24px;font-size:18px;font-weight:600;">Hi {{ _vars_.first_name or _rcpt_email_ }},</td>
163
+ </tr>
164
+ <tr>
165
+ <td style="padding:0 0 16px;font-size:15px;line-height:1.6;">
166
+ <p style="margin:0 0 12px;">Welcome to MyOrg! Your workspace <strong>{{ _vars_.workspace or 'Starter Plan' }}</strong> is ready.</p>
167
+ <p style="margin:0;">Use the button below to confirm your email and finish the setup.</p>
168
+ </td>
169
+ </tr>
170
+ <tr>
171
+ <td align="center" style="padding:12px 0 28px;">
172
+ <a href="{{ _vars_.cta_url or '#' }}" style="display:inline-block;background:#2563eb;color:#ffffff;text-decoration:none;padding:14px 28px;border-radius:6px;font-size:15px;">Confirm email</a>
173
+ </td>
174
+ </tr>
175
+ <tr>
176
+ <td style="font-size:13px;line-height:1.5;color:#6b7280;">
177
+ <p style="margin:0 0 8px;">If you did not sign up for MyOrg, you can ignore this email.</p>
178
+ <p style="margin:0;">Need help? Reply to this message or visit {{ _vars_.support_url or 'https://myorg.com/support' }}.</p>
179
+ </td>
180
+ </tr>
181
+ </table>
182
+ </td>
183
+ </tr>
184
+ </table>
185
+
186
+ {% include 'partials/footer.njk' %}
187
+ ```
188
+
189
+ ### 4.3 Contact form partials (`form-template/partials`)
190
+
191
+ `$CONFIG_ROOT/myorg.com/form-template/partials/header.njk`
192
+
193
+ ```njk
194
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#102347;color:#ffffff;padding:18px 0;font-family:Arial,sans-serif;">
195
+ <tr>
196
+ <td align="center" style="font-size:20px;font-weight:600;">New MyOrg contact form submission</td>
197
+ </tr>
198
+ </table>
199
+ ```
200
+
201
+ `$CONFIG_ROOT/myorg.com/form-template/partials/footer.njk`
202
+
203
+ ```njk
204
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f4f6fb;color:#6b7280;padding:16px 0;font-family:Arial,sans-serif;font-size:12px;">
205
+ <tr>
206
+ <td align="center">
207
+ <p style="margin:0;">Delivered by Mail Magic for MyOrg.</p>
208
+ </td>
209
+ </tr>
210
+ </table>
211
+ ```
212
+
213
+ ### 4.4 Contact form template (`form-template/contact.njk`)
214
+
215
+ `$CONFIG_ROOT/myorg.com/form-template/contact.njk`
216
+
217
+ ```njk
218
+ {% include 'partials/header.njk' %}
219
+
220
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#ffffff;padding:24px 0;font-family:Arial,sans-serif;color:#111827;">
221
+ <tr>
222
+ <td align="center">
223
+ <table role="presentation" width="640" cellpadding="0" cellspacing="0" style="max-width:94%;text-align:left;border:1px solid #e5e7eb;border-radius:6px;overflow:hidden;">
224
+ <tr>
225
+ <td style="padding:20px 24px;font-size:16px;font-weight:600;background:#f9fafb;">Submitted fields</td>
226
+ </tr>
227
+ <tr>
228
+ <td style="padding:16px 24px;">
229
+ {% if _fields_ %}
230
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="font-size:14px;">
231
+ {% for field, value in _fields_ %}
232
+ <tr>
233
+ <td style="padding:8px 0;color:#6b7280;width:180px;">{{ field | replace('_', ' ') | title }}</td>
234
+ <td style="padding:8px 0;color:#111827;">{{ value }}</td>
235
+ </tr>
236
+ {% endfor %}
237
+ </table>
238
+ {% else %}
239
+ <p>No form fields were included.</p>
240
+ {% endif %}
241
+ </td>
242
+ </tr>
243
+ {% if _meta_ %}
244
+ <tr>
245
+ <td style="padding:16px 24px;background:#f9fafb;color:#4b5563;font-size:12px;">
246
+ <strong>Sender IP:</strong> {{ _meta_.client_ip | default('unknown') }} ·
247
+ <strong>Received at:</strong> {{ _meta_.received_at | default(now().iso8601()) }}
248
+ </td>
249
+ </tr>
250
+ {% endif %}
251
+ </table>
252
+ </td>
253
+ </tr>
254
+ </table>
255
+
256
+ {% include 'partials/footer.njk' %}
257
+ ```
258
+
259
+ ### 4.5 Provide the logo asset
260
+
261
+ Copy or design a square PNG logo and add it to both asset folders so the inline references resolve (Mail Magic resolves assets per template type):
262
+
263
+ ```bash
264
+ cp path/to/myorg-logo.png "$CONFIG_ROOT"/myorg.com/tx-template/assets/logo.png
265
+ cp path/to/myorg-logo.png "$CONFIG_ROOT"/myorg.com/form-template/assets/logo.png
266
+ ```
267
+
268
+ The inline flag (`true`) in `asset('logo.png', true)` tells Mail Magic to attach the image and rewrite the markup to `cid:logo.png` when messages are flattened.
269
+
270
+ ---
271
+
272
+ ## 5. Start Mail Magic and verify
273
+
274
+ 1. Restart `mail-magic` (or run `npm run dev`) so it picks up the new `CONFIG_PATH`.
275
+ 2. Confirm the bootstrap worked — the logs should mention importing user `myorg` and domain `myorg.com`.
276
+ 3. Trigger a transactional email:
277
+ ```bash
278
+ curl -X POST http://localhost:3000/v1/tx/message \
279
+ -H 'Content-Type: application/json' \
280
+ -H 'X-API-Token: <your 32-char token>' \
281
+ -d '{
282
+ "user": "myorg",
283
+ "domain": "myorg.com",
284
+ "slug": "welcome",
285
+ "to": "new.user@myorg.com",
286
+ "variables": {"first_name": "Kai", "cta_url": "https://myorg.com/confirm"}
287
+ }'
288
+ ```
289
+ 4. Trigger the contact form template the same way your frontend will post to `/v1/form/message` (Supply `form_id` or `idname` of `contact`). With `DB_AUTO_RELOAD=1`, editing the templates or assets is as simple as saving the file.
290
+
291
+ You now have a clean, self-contained configuration for MyOrg that inherits Mail Magic behaviour while keeping templates, partials, and assets under version control in a dedicated folder.
package/dist/api/forms.js CHANGED
@@ -46,7 +46,7 @@ export class FormAPI extends ApiModule {
46
46
  const formSlug = normalizeSlug(idname);
47
47
  const localeSlug = normalizeSlug(resolvedLocale || domain.locale || user.locale || '');
48
48
  const slug = `${userSlug}-${domainSlug}${localeSlug ? '-' + localeSlug : ''}-${formSlug}`;
49
- const filenameParts = [userSlug, domainSlug, 'form-template'];
49
+ const filenameParts = [domainSlug, 'form-template'];
50
50
  if (localeSlug) {
51
51
  filenameParts.push(localeSlug);
52
52
  }
@@ -138,14 +138,14 @@ export async function upsert_form(record) {
138
138
  record.slug = `${idname}-${dname}${locale ? '-' + locale : ''}-${name}`;
139
139
  }
140
140
  if (!record.filename) {
141
- const parts = [idname, dname, 'form-template'];
141
+ const parts = [dname, 'form-template'];
142
142
  if (locale)
143
143
  parts.push(locale);
144
144
  parts.push(name);
145
145
  record.filename = path.join(...parts);
146
146
  }
147
147
  else {
148
- record.filename = path.join(idname, dname, 'form-template', record.filename);
148
+ record.filename = path.join(dname, 'form-template', record.filename);
149
149
  }
150
150
  if (!record.filename.endsWith('.njk')) {
151
151
  record.filename += '.njk';
@@ -63,9 +63,9 @@ function extractAndReplaceAssets(html, opts) {
63
63
  return { html: replacedHtml, assets };
64
64
  }
65
65
  async function _load_template(store, filename, pathname, user, domain, locale, type) {
66
- const rootDir = path.join(store.configpath, user.idname, domain.name, type);
66
+ const rootDir = path.join(store.configpath, domain.name, type);
67
67
  let relFile = filename;
68
- const prefix = path.join(user.idname, domain.name, type) + path.sep;
68
+ const prefix = path.join(domain.name, type) + path.sep;
69
69
  if (filename.startsWith(prefix)) {
70
70
  relFile = filename.slice(prefix.length);
71
71
  }
@@ -81,15 +81,15 @@ async function _load_template(store, filename, pathname, user, domain, locale, t
81
81
  throw new Error(`Template file "${absPath}" is empty`);
82
82
  }
83
83
  try {
84
- const baseUserPath = path.join(store.configpath, user.idname);
85
- const templateKey = path.relative(baseUserPath, absPath);
84
+ const baseConfigPath = store.configpath;
85
+ const templateKey = path.relative(baseConfigPath, absPath);
86
86
  if (!templateKey) {
87
87
  throw new Error(`Unable to resolve template path for "${absPath}"`);
88
88
  }
89
- const processor = new Unyuck({ basePath: baseUserPath });
89
+ const processor = new Unyuck({ basePath: baseConfigPath });
90
90
  const merged = processor.flattenNoAssets(templateKey);
91
91
  const { html, assets } = extractAndReplaceAssets(merged, {
92
- basePath: path.join(store.configpath, user.idname),
92
+ basePath: baseConfigPath,
93
93
  type,
94
94
  domainName: domain.name,
95
95
  locale,
@@ -33,14 +33,14 @@ export async function upsert_txmail(record) {
33
33
  record.slug = `${idname}-${dname}${locale ? '-' + locale : ''}-${name}`;
34
34
  }
35
35
  if (!record.filename) {
36
- const parts = [idname, dname, 'tx-template'];
36
+ const parts = [dname, 'tx-template'];
37
37
  if (locale)
38
38
  parts.push(locale);
39
39
  parts.push(name);
40
40
  record.filename = path.join(...parts);
41
41
  }
42
42
  else {
43
- record.filename = path.join(idname, dname, 'tx-template', record.filename);
43
+ record.filename = path.join(dname, 'tx-template', record.filename);
44
44
  }
45
45
  if (!record.filename.endsWith('.njk')) {
46
46
  record.filename += '.njk';
@@ -146,13 +146,12 @@ export async function init_api_txmail(api_db) {
146
146
  api_txmail.addHook('beforeValidate', async (template) => {
147
147
  const { user, domain } = await user_and_domain(template.domain_id);
148
148
  console.log('HERE');
149
- const idname = normalizeSlug(user.idname);
150
149
  const dname = normalizeSlug(domain.name);
151
150
  const name = normalizeSlug(template.name);
152
151
  const locale = normalizeSlug(template.locale || domain.locale || user.locale || '');
153
- template.slug ||= `${idname}-${dname}${locale ? '-' + locale : ''}-${name}`;
152
+ template.slug ||= `${normalizeSlug(user.idname)}-${dname}${locale ? '-' + locale : ''}-${name}`;
154
153
  if (!template.filename) {
155
- const parts = [idname, dname, 'tx-template'];
154
+ const parts = [dname, 'tx-template'];
156
155
  if (locale)
157
156
  parts.push(locale);
158
157
  parts.push(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technomoron/mail-magic",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "repository": {
package/src/api/forms.ts CHANGED
@@ -65,7 +65,7 @@ export class FormAPI extends ApiModule<mailApiServer> {
65
65
  const formSlug = normalizeSlug(idname);
66
66
  const localeSlug = normalizeSlug(resolvedLocale || domain.locale || user.locale || '');
67
67
  const slug = `${userSlug}-${domainSlug}${localeSlug ? '-' + localeSlug : ''}-${formSlug}`;
68
- const filenameParts = [userSlug, domainSlug, 'form-template'];
68
+ const filenameParts = [domainSlug, 'form-template'];
69
69
  if (localeSlug) {
70
70
  filenameParts.push(localeSlug);
71
71
  }
@@ -169,12 +169,12 @@ export async function upsert_form(record: api_form_type): Promise<api_form> {
169
169
  }
170
170
 
171
171
  if (!record.filename) {
172
- const parts = [idname, dname, 'form-template'];
172
+ const parts = [dname, 'form-template'];
173
173
  if (locale) parts.push(locale);
174
174
  parts.push(name);
175
175
  record.filename = path.join(...parts);
176
176
  } else {
177
- record.filename = path.join(idname, dname, 'form-template', record.filename);
177
+ record.filename = path.join(dname, 'form-template', record.filename);
178
178
  }
179
179
  if (!record.filename.endsWith('.njk')) {
180
180
  record.filename += '.njk';
@@ -112,10 +112,10 @@ async function _load_template(
112
112
  locale: string | null,
113
113
  type: 'form-template' | 'tx-template'
114
114
  ): Promise<LoadedTemplate> {
115
- const rootDir = path.join(store.configpath, user.idname, domain.name, type);
115
+ const rootDir = path.join(store.configpath, domain.name, type);
116
116
 
117
117
  let relFile = filename;
118
- const prefix = path.join(user.idname, domain.name, type) + path.sep;
118
+ const prefix = path.join(domain.name, type) + path.sep;
119
119
  if (filename.startsWith(prefix)) {
120
120
  relFile = filename.slice(prefix.length);
121
121
  }
@@ -135,17 +135,17 @@ async function _load_template(
135
135
  }
136
136
 
137
137
  try {
138
- const baseUserPath = path.join(store.configpath, user.idname);
139
- const templateKey = path.relative(baseUserPath, absPath);
138
+ const baseConfigPath = store.configpath;
139
+ const templateKey = path.relative(baseConfigPath, absPath);
140
140
  if (!templateKey) {
141
141
  throw new Error(`Unable to resolve template path for "${absPath}"`);
142
142
  }
143
143
 
144
- const processor = new Unyuck({ basePath: baseUserPath });
144
+ const processor = new Unyuck({ basePath: baseConfigPath });
145
145
  const merged = processor.flattenNoAssets(templateKey);
146
146
 
147
147
  const { html, assets } = extractAndReplaceAssets(merged, {
148
- basePath: path.join(store.configpath, user.idname),
148
+ basePath: baseConfigPath,
149
149
  type,
150
150
  domainName: domain.name,
151
151
  locale,
@@ -56,12 +56,12 @@ export async function upsert_txmail(record: api_txmail_type): Promise<api_txmail
56
56
  }
57
57
 
58
58
  if (!record.filename) {
59
- const parts = [idname, dname, 'tx-template'];
59
+ const parts = [dname, 'tx-template'];
60
60
  if (locale) parts.push(locale);
61
61
  parts.push(name);
62
62
  record.filename = path.join(...parts);
63
63
  } else {
64
- record.filename = path.join(idname, dname, 'tx-template', record.filename);
64
+ record.filename = path.join(dname, 'tx-template', record.filename);
65
65
  }
66
66
  if (!record.filename.endsWith('.njk')) {
67
67
  record.filename += '.njk';
@@ -175,15 +175,14 @@ export async function init_api_txmail(api_db: Sequelize): Promise<typeof api_txm
175
175
 
176
176
  console.log('HERE');
177
177
 
178
- const idname = normalizeSlug(user.idname);
179
178
  const dname = normalizeSlug(domain.name);
180
179
  const name = normalizeSlug(template.name);
181
180
  const locale = normalizeSlug(template.locale || domain.locale || user.locale || '');
182
181
 
183
- template.slug ||= `${idname}-${dname}${locale ? '-' + locale : ''}-${name}`;
182
+ template.slug ||= `${normalizeSlug(user.idname)}-${dname}${locale ? '-' + locale : ''}-${name}`;
184
183
 
185
184
  if (!template.filename) {
186
- const parts = [idname, dname, 'tx-template'];
185
+ const parts = [dname, 'tx-template'];
187
186
  if (locale) parts.push(locale);
188
187
  parts.push(name);
189
188
  template.filename = parts.join('/');
package/test1.sh DELETED
@@ -1,13 +0,0 @@
1
- #!/bin/sh
2
-
3
- curl -X POST http://localhost:3776/api/v1/form/message \
4
- -F "formid=testform" \
5
- -F "domain=ml.yesmedia.no" \
6
- -F "vars={\"x\":\"y\"}" \
7
- -F "attachment1=@./tsconfig.json" \
8
- -F "attachment2=@./config/testuser/ml.yesmedia.no/form-template/assets/3075977.png"
9
-
10
- # -F "rcpt=bjornjac@pm.me" \
11
- # -H "Authorization: Bearer apikey-j82lkIOjUuj34sd" \
12
-
13
-