feat: wire GHL webhook to Resend email templates

- Replace raw Resend text emails with sendMail using Resend templates
- automated-user-registration template: firstName, tempPassword,
  verifyUrl, loginUrl
- license-key-delivery template: firstName, license_key
- Remove requestPasswordReset call (temp password is the one-time
  credential; verification link handles email confirmation)
- Add .catch() to Better Auth email callbacks to prevent unhandled
  rejections from missing templates crashing the server
This commit is contained in:
NeilClack
2026-07-08 15:01:08 -05:00
parent e6ef7d3b36
commit 55a29b9c1a
29 changed files with 26572 additions and 21641 deletions
@@ -108,17 +108,16 @@
<select
id="field-solfeggioFrequencyId"
name="solfeggioFrequencyId"
onchange={(e) => solfeggioFrequencyId = (e.target as HTMLSelectElement).value}
onchange={(e) => (solfeggioFrequencyId = (e.target as HTMLSelectElement).value)}
class="w-full border px-3 py-1.5 text-sm {fieldErrors.solfeggioFrequencyId
? 'border-red-400'
: 'border-gray-300 dark:border-gray-600'} dark:bg-gray-800 dark:text-gray-100"
>
<option value="">-- Select Frequency --</option>
{#each relationOptionsData.solfeggioFrequencyId ?? [] as opt (String(opt.value))}
<option
value={opt.value}
selected={String(opt.value) === solfeggioFrequencyId}
>{opt.label}</option>
<option value={opt.value} selected={String(opt.value) === solfeggioFrequencyId}
>{opt.label}</option
>
{/each}
</select>
{#if fieldErrors.solfeggioFrequencyId}
@@ -143,10 +142,7 @@
>
<option value="">-- Select Category --</option>
{#each parentOptions as cat (String(cat.id))}
<option
value={cat.id}
selected={String(cat.id) === parentCategoryId}
>{cat.name}</option>
<option value={cat.id} selected={String(cat.id) === parentCategoryId}>{cat.name}</option>
{/each}
</select>
{#if fieldErrors.parentCategoryId}
@@ -164,17 +160,14 @@
<select
id="field-categoryId"
name="categoryId"
onchange={(e) => categoryId = (e.target as HTMLSelectElement).value}
onchange={(e) => (categoryId = (e.target as HTMLSelectElement).value)}
class="w-full border px-3 py-1.5 text-sm {fieldErrors.categoryId
? 'border-red-400'
: 'border-gray-300 dark:border-gray-600'} dark:bg-gray-800 dark:text-gray-100"
>
<option value="">-- Select Sub-Category --</option>
{#each childOptions as cat (String(cat.id))}
<option
value={cat.id}
selected={String(cat.id) === categoryId}
>{cat.name}</option>
<option value={cat.id} selected={String(cat.id) === categoryId}>{cat.name}</option>
{/each}
</select>
{#if fieldErrors.categoryId}
@@ -192,17 +185,16 @@
<select
id="field-subscriptionLevelId"
name="subscriptionLevelId"
onchange={(e) => subscriptionLevelId = (e.target as HTMLSelectElement).value}
onchange={(e) => (subscriptionLevelId = (e.target as HTMLSelectElement).value)}
class="w-full border px-3 py-1.5 text-sm {fieldErrors.subscriptionLevelId
? 'border-red-400'
: 'border-gray-300 dark:border-gray-600'} dark:bg-gray-800 dark:text-gray-100"
>
<option value="">-- Select Level --</option>
{#each relationOptionsData.subscriptionLevelId ?? [] as opt (String(opt.value))}
<option
value={opt.value}
selected={String(opt.value) === subscriptionLevelId}
>{opt.label}</option>
<option value={opt.value} selected={String(opt.value) === subscriptionLevelId}
>{opt.label}</option
>
{/each}
</select>
{#if fieldErrors.subscriptionLevelId}
@@ -218,14 +210,14 @@
>
Content
</label>
<div id="field-content">
{#key record.id}
<TipTap
initialContent={typeof record.content === 'string' ? record.content : ''}
name="content"
/>
{/key}
</div>
<div id="field-content">
{#key record.id}
<TipTap
initialContent={typeof record.content === 'string' ? record.content : ''}
name="content"
/>
{/key}
</div>
{#if fieldErrors.content}
<p class="mt-1 text-xs text-red-600">{fieldErrors.content}</p>
{/if}