mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-12 04:15:39 +08:00
Add waitlist invitation sending from admin
This commit is contained in:
50
server/utils/invitations.ts
Normal file
50
server/utils/invitations.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
let cachedHtmlTemplate: string | null = null
|
||||
|
||||
export function generateInvitationCode() {
|
||||
return randomBytes(4).toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
async function loadInvitationTemplate() {
|
||||
if (cachedHtmlTemplate) {
|
||||
return cachedHtmlTemplate
|
||||
}
|
||||
|
||||
const templatePath = join(process.cwd(), 'public', 'docs', 'invites.html')
|
||||
|
||||
try {
|
||||
const file = await readFile(templatePath, 'utf8')
|
||||
cachedHtmlTemplate = file
|
||||
return file
|
||||
} catch (error) {
|
||||
console.warn('Could not load invitation email template, falling back to plain HTML.', error)
|
||||
const fallback = `<!DOCTYPE html><html><body><p>Your OpenSquawk invite code: <strong>{{INVITE_CODE}}</strong></p></body></html>`
|
||||
cachedHtmlTemplate = fallback
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
export async function renderInvitationEmail(code: string) {
|
||||
const template = await loadInvitationTemplate()
|
||||
return template.replace(/{{INVITE_CODE}}/g, code)
|
||||
}
|
||||
|
||||
export function renderInvitationText(code: string) {
|
||||
const lines = [
|
||||
'Welcome to OpenSquawk!',
|
||||
'',
|
||||
'Thanks for joining the waitlist — here is your personal invite code:',
|
||||
code,
|
||||
'',
|
||||
'Use it to register your account:',
|
||||
'https://opensquawk.de/login?mode=register',
|
||||
'',
|
||||
'Blue skies,',
|
||||
'Your OpenSquawk crew',
|
||||
]
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
@@ -3,7 +3,8 @@ const ADMIN_EMAIL_FALLBACK = 'info@opensquawk.de'
|
||||
interface MailOptions {
|
||||
to: string
|
||||
subject: string
|
||||
text: string
|
||||
text?: string
|
||||
html?: string
|
||||
from?: string
|
||||
}
|
||||
|
||||
@@ -81,6 +82,7 @@ async function sendViaSmtp(payload: MailPayload) {
|
||||
to: payload.to,
|
||||
subject: payload.subject,
|
||||
text: payload.text,
|
||||
html: payload.html,
|
||||
})
|
||||
return true
|
||||
} catch (error) {
|
||||
@@ -90,6 +92,10 @@ async function sendViaSmtp(payload: MailPayload) {
|
||||
}
|
||||
|
||||
export async function sendMail(options: MailOptions) {
|
||||
if (!options.text && !options.html) {
|
||||
throw new Error('Email payload requires text or html content')
|
||||
}
|
||||
|
||||
const payload: MailPayload = {
|
||||
...options,
|
||||
from: resolveFrom(options.from),
|
||||
@@ -97,7 +103,8 @@ export async function sendMail(options: MailOptions) {
|
||||
|
||||
const success = await sendViaSmtp(payload)
|
||||
if (!success) {
|
||||
console.info(`[mail:fallback] ${options.subject}\nRecipient: ${options.to}\n${options.text}`)
|
||||
const preview = options.text || options.html || ''
|
||||
console.info(`[mail:fallback] ${options.subject}\nRecipient: ${options.to}\n${preview}`)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user