@flowselections/actie-mail-kwekers-erp-mathot 1.0.20 → 1.0.21

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.
@@ -214,18 +214,18 @@ export const sendCampaign = createServerFn({ method: "POST" })
214
214
  const stickerLine = p.sticker_cost && Number(p.sticker_cost) > 0
215
215
  ? `<div style="margin-top:12px;background:#eff6ff;border-left:4px solid #3b82f6;padding:10px 12px;border-radius:4px;font-size:14px;color:#1e3a8a;">📋 <strong>Eigen sticker opdrukken:</strong> ${fmtEuro(Number(p.sticker_cost))} per stuk</div>`
216
216
  : "";
217
- return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="border:1px solid #e5e7eb;border-radius:8px;margin:0 0 16px 0;background:#fff;">
218
- <tr>
219
- <td style="padding:14px;vertical-align:top;width:112px;">${imageCell}</td>
220
- <td style="padding:14px 14px 14px 0;vertical-align:top;">
221
- <div style="font-size:16px;font-weight:700;color:#111;line-height:1.3;">${escapeHtml(p.product_name)}</div>
222
- ${p.plants_per_crate ? `<div style="font-size:13px;color:#6b7280;margin-top:2px;">${p.plants_per_crate} planten per fust</div>` : ""}
223
- ${p.barcode ? `<div style="font-size:13px;color:#6b7280;margin-top:2px;">Barcode: ${escapeHtml(String(p.barcode))}</div>` : ""}
224
- ${normal > 0 ? `<div style="font-size:13px;color:#9ca3af;text-decoration:line-through;margin-top:6px;">Origineel: ${fmtEuro(normal)}</div>` : ""}
225
- <div style="margin-top:6px;">${tierChips}</div>
226
- </td>
227
- </tr>
228
- ${stickerLine ? `<tr><td colspan="2" style="padding:0 14px 14px 14px;">${stickerLine}</td></tr>` : ""}
217
+ return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="border:1px solid #e5e7eb;border-radius:8px;margin:0 0 16px 0;background:#fff;">
218
+ <tr>
219
+ <td style="padding:14px;vertical-align:top;width:112px;">${imageCell}</td>
220
+ <td style="padding:14px 14px 14px 0;vertical-align:top;">
221
+ <div style="font-size:16px;font-weight:700;color:#111;line-height:1.3;">${escapeHtml(p.product_name)}</div>
222
+ ${p.plants_per_crate ? `<div style="font-size:13px;color:#6b7280;margin-top:2px;">${p.plants_per_crate} planten per fust</div>` : ""}
223
+ ${p.barcode ? `<div style="font-size:13px;color:#6b7280;margin-top:2px;">Barcode: ${escapeHtml(String(p.barcode))}</div>` : ""}
224
+ ${normal > 0 ? `<div style="font-size:13px;color:#9ca3af;text-decoration:line-through;margin-top:6px;">Origineel: ${fmtEuro(normal)}</div>` : ""}
225
+ <div style="margin-top:6px;">${tierChips}</div>
226
+ </td>
227
+ </tr>
228
+ ${stickerLine ? `<tr><td colspan="2" style="padding:0 14px 14px 14px;">${stickerLine}</td></tr>` : ""}
229
229
  </table>`;
230
230
  };
231
231
  const productsHtml = (products ?? []).map(renderProduct).join("");
@@ -234,43 +234,43 @@ export const sendCampaign = createServerFn({ method: "POST" })
234
234
  const failed = [];
235
235
  for (const r of recipients ?? []) {
236
236
  const actionUrl = `${origin}/actie/${r.link_code}`;
237
- const html = `<!doctype html><html><body style="margin:0;padding:0;font-family:Arial,Helvetica,sans-serif;background:#f3f4f6;">
238
- <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;padding:24px 0;">
239
- <tr><td align="center">
240
- <table role="presentation" cellpadding="0" cellspacing="0" width="600" style="max-width:600px;width:100%;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,0.06);">
241
- <tr>
242
- <td bgcolor="${brand.primary}" style="background-color:${brand.primary};background-image:linear-gradient(135deg,${brand.primary},${brand.primaryDark});padding:28px 24px;text-align:center;">
243
- ${brand.logoUrl ? `<div style="margin:0 0 12px 0;"><img src="${escapeHtml(brand.logoUrl)}" alt="${escapeHtml(brand.brandName ?? "")}" height="40" style="display:inline-block;height:40px;max-width:220px;width:auto;border:0;" /></div>` : ""}
244
- <div style="font-size:24px;font-weight:800;color:#ffffff;letter-spacing:-0.01em;">${escapeHtml(campaign.subject)}</div>
245
- </td>
246
- </tr>
247
- <tr>
248
- <td style="padding:24px;background:#f9fafb;">
249
- <p style="margin:0 0 12px 0;color:#111;font-size:15px;">Beste ${escapeHtml(r.recipient_name)},</p>
250
- ${campaign.intro_message ? `<p style="margin:0 0 20px 0;color:#374151;font-size:15px;white-space:pre-line;line-height:1.5;">${escapeHtml(campaign.intro_message)}</p>` : `<p style="margin:0 0 20px 0;color:#374151;font-size:15px;">Wij hebben een mooie aanbieding voor u!</p>`}
251
-
252
- ${productsHtml ? `<h2 style="margin:24px 0 8px 0;color:${brand.primaryText};font-size:22px;border-bottom:2px solid ${brand.primary};padding-bottom:6px;">Aanbiedingen</h2>${productsHtml}` : ""}
253
-
254
- <div style="margin:16px 0;background:#fef3c7;border-left:4px solid #f59e0b;padding:12px 14px;border-radius:4px;color:#92400e;font-size:14px;">
255
- ⏰ <strong>Actie geldig zolang de voorraad strekt!</strong>
256
- </div>
257
-
258
- <p style="margin:28px 0;text-align:center;">
259
- <a href="${actionUrl}" style="background:${brand.button};color:${brand.buttonText};padding:14px 28px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:700;font-size:16px;">🛒 Direct Bestellen</a>
260
- </p>
261
-
262
- <p style="color:#6b7280;font-size:12px;text-align:center;margin:16px 0 0 0;">Of <a href="${actionUrl}" style="color:${brand.primaryText};text-decoration:underline;">open deze link</a>.</p>
263
- </td>
264
- </tr>
265
- <tr>
266
- <td style="padding:18px 24px;background:#ffffff;border-top:1px solid #e5e7eb;color:#6b7280;font-size:12px;text-align:center;">
267
- Met vriendelijke groet,<br/><strong>${escapeHtml(campaign.from_name ?? brand.brandName ?? "")}</strong>
268
- </td>
269
- </tr>
270
- </table>
271
- </td></tr>
272
- </table>
273
- ${trackingEnabled ? `<img src="${origin}/api/public/pixel/${r.link_code}.gif" width="1" height="1" alt="" style="display:block;border:0;width:1px;height:1px;" />` : ""}
237
+ const html = `<!doctype html><html><body style="margin:0;padding:0;font-family:Arial,Helvetica,sans-serif;background:#f3f4f6;">
238
+ <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;padding:24px 0;">
239
+ <tr><td align="center">
240
+ <table role="presentation" cellpadding="0" cellspacing="0" width="600" style="max-width:600px;width:100%;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,0.06);">
241
+ <tr>
242
+ <td bgcolor="${brand.primary}" style="background-color:${brand.primary};background-image:linear-gradient(135deg,${brand.primary},${brand.primaryDark});padding:28px 24px;text-align:center;">
243
+ ${brand.logoUrl ? `<div style="margin:0 0 12px 0;"><img src="${escapeHtml(brand.logoUrl)}" alt="${escapeHtml(brand.brandName ?? "")}" height="40" style="display:inline-block;height:40px;max-width:220px;width:auto;border:0;" /></div>` : ""}
244
+ <div style="font-size:24px;font-weight:800;color:#ffffff;letter-spacing:-0.01em;">${escapeHtml(campaign.subject)}</div>
245
+ </td>
246
+ </tr>
247
+ <tr>
248
+ <td style="padding:24px;background:#f9fafb;">
249
+ <p style="margin:0 0 12px 0;color:#111;font-size:15px;">Beste ${escapeHtml(r.recipient_name)},</p>
250
+ ${campaign.intro_message ? `<p style="margin:0 0 20px 0;color:#374151;font-size:15px;white-space:pre-line;line-height:1.5;">${escapeHtml(campaign.intro_message)}</p>` : `<p style="margin:0 0 20px 0;color:#374151;font-size:15px;">Wij hebben een mooie aanbieding voor u!</p>`}
251
+
252
+ ${productsHtml ? `<h2 style="margin:24px 0 8px 0;color:${brand.primaryText};font-size:22px;border-bottom:2px solid ${brand.primary};padding-bottom:6px;">Aanbiedingen</h2>${productsHtml}` : ""}
253
+
254
+ <div style="margin:16px 0;background:#fef3c7;border-left:4px solid #f59e0b;padding:12px 14px;border-radius:4px;color:#92400e;font-size:14px;">
255
+ ⏰ <strong>Actie geldig zolang de voorraad strekt!</strong>
256
+ </div>
257
+
258
+ <p style="margin:28px 0;text-align:center;">
259
+ <a href="${actionUrl}" style="background:${brand.button};color:${brand.buttonText};padding:14px 28px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:700;font-size:16px;">🛒 Direct Bestellen</a>
260
+ </p>
261
+
262
+ <p style="color:#6b7280;font-size:12px;text-align:center;margin:16px 0 0 0;">Of <a href="${actionUrl}" style="color:${brand.primaryText};text-decoration:underline;">open deze link</a>.</p>
263
+ </td>
264
+ </tr>
265
+ <tr>
266
+ <td style="padding:18px 24px;background:#ffffff;border-top:1px solid #e5e7eb;color:#6b7280;font-size:12px;text-align:center;">
267
+ Met vriendelijke groet,<br/><strong>${escapeHtml(campaign.from_name ?? brand.brandName ?? "")}</strong>
268
+ </td>
269
+ </tr>
270
+ </table>
271
+ </td></tr>
272
+ </table>
273
+ ${trackingEnabled ? `<img src="${origin}/api/public/pixel/${r.link_code}.gif" width="1" height="1" alt="" style="display:block;border:0;width:1px;height:1px;" />` : ""}
274
274
  </body></html>`;
275
275
  try {
276
276
  const { error: insErr } = await supabase
@@ -320,25 +320,35 @@ export const sendCampaign = createServerFn({ method: "POST" })
320
320
  .update({ status: "sent", sent_at: now })
321
321
  .eq("id", data.campaignId);
322
322
  }
323
- // Trigger de outbox-dispatcher onmiddellijk, zodat we niet hoeven te wachten
324
- // tot de cron (elke minuut) draait. Fire-and-forget: fouten hier zijn niet
325
- // fataal — de cron pikt de mails alsnog op als vangnet.
323
+ // Trigger de outbox-dispatcher van de EIGEN shell onmiddellijk, zodat we niet
324
+ // hoeven te wachten tot de cron (elke minuut) draait. De cron blijft het vangnet:
325
+ // faalt deze aanroep, dan worden de mails alsnog verstuurd — maar nooit stil,
326
+ // een mislukking wordt altijd gelogd.
326
327
  if (sentIds.length > 0) {
327
- try {
328
- const anonKey = process.env.SUPABASE_ANON_KEY ??
329
- process.env.SUPABASE_PUBLISHABLE_KEY ??
330
- process.env.VITE_SUPABASE_PUBLISHABLE_KEY ??
331
- "";
332
- void fetch("https://e-mail-authenticatie-module.lovable.app/api/public/email-auth/outbox-process", {
333
- method: "POST",
334
- headers: {
335
- "Content-Type": "application/json",
336
- apikey: anonKey,
337
- },
338
- body: "{}",
339
- }).catch(() => { });
328
+ const dispatchBase = (process.env.PUBLIC_BASE_URL ?? origin ?? "").replace(/\/$/, "");
329
+ if (!dispatchBase) {
330
+ console.warn("[aanbiedingsmailtje] Outbox-dispatcher overgeslagen: geen basis-URL bekend " +
331
+ "(stel PUBLIC_BASE_URL in). De cron verwerkt de wachtrij binnen een minuut.");
332
+ }
333
+ else {
334
+ const dispatchUrl = `${dispatchBase}/api/public/email-auth/outbox-process`;
335
+ try {
336
+ const res = await fetch(dispatchUrl, {
337
+ method: "POST",
338
+ headers: { "Content-Type": "application/json" },
339
+ body: "{}",
340
+ });
341
+ if (!res.ok) {
342
+ const detail = await res.text().catch(() => "");
343
+ console.warn(`[aanbiedingsmailtje] Outbox-dispatcher gaf ${res.status} ${res.statusText} ` +
344
+ `voor ${dispatchUrl}: ${detail.slice(0, 300)}. De cron verwerkt de wachtrij als vangnet.`);
345
+ }
346
+ }
347
+ catch (e) {
348
+ console.warn(`[aanbiedingsmailtje] Outbox-dispatcher onbereikbaar op ${dispatchUrl}: ` +
349
+ `${String(e?.message ?? e)}. De cron verwerkt de wachtrij als vangnet.`);
350
+ }
340
351
  }
341
- catch { /* noop */ }
342
352
  }
343
353
  return { sent: sentIds.length, failed: failed.length, errors: failed.slice(0, 5) };
344
354
  });