added admin email notification

This commit is contained in:
Corey Blais
2026-04-15 17:16:35 -04:00
parent 784a911dc2
commit ac0cc122d3
4 changed files with 84 additions and 0 deletions
+1
View File
@@ -6,3 +6,4 @@ BACKEND_URL=http://localhost:5000
VITE_API_BASE_URL=http://localhost:5000/api
NODE_ENV=development
ADMIN_EMAILS=corey@blaishome.online
RESCUE_STATUS_NOTIFICATION_EMAIL=appadmin@flockpal.app
+81
View File
@@ -219,6 +219,7 @@ const smtpUser = process.env.SMTP_USER?.trim() ?? '';
const smtpPass = process.env.SMTP_PASS?.trim() ?? '';
const smtpFromEmail = process.env.SMTP_FROM_EMAIL?.trim() ?? '';
const smtpFromName = process.env.SMTP_FROM_NAME?.trim() || 'FlockPal';
const rescueStatusNotificationEmail = process.env.RESCUE_STATUS_NOTIFICATION_EMAIL?.trim() || 'appadmin@flockpal.app';
const adminEmails = new Set(
(process.env.ADMIN_EMAILS ?? '')
.split(',')
@@ -487,6 +488,64 @@ const sendMagicLink = async ({
};
};
const escapeHtml = (value: string) =>
value
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
const sendRescueStatusNotification = async ({
workspace,
ownerEmail,
event,
}: {
workspace: WorkspaceRow;
ownerEmail: string | null;
event: 'created' | 'converted' | 'status_changed';
}) => {
const statusLabel = workspace.rescue_verification_status.replace(/_/g, ' ');
const eventLabel = event === 'created' ? 'created' : event === 'converted' ? 'converted to rescue' : 'status updated';
const subject = `FlockPal rescue status: ${workspace.name} ${eventLabel}`;
const escapedWorkspaceName = escapeHtml(workspace.name);
const escapedStatusLabel = escapeHtml(statusLabel);
const escapedOwnerEmail = escapeHtml(ownerEmail ?? 'unknown');
const escapedBillingEmail = escapeHtml(workspace.billing_email ?? 'not set');
const lines = [
`Rescue flock: ${workspace.name}`,
`Event: ${eventLabel}`,
`Verification status: ${statusLabel}`,
`Owner email: ${ownerEmail ?? 'unknown'}`,
`Billing email: ${workspace.billing_email ?? 'not set'}`,
`Flock ID: ${workspace.id}`,
];
if (!mailTransport) {
console.log(`Rescue status notification for ${rescueStatusNotificationEmail}:\n${lines.join('\n')}`);
return { delivered: false };
}
await mailTransport.sendMail({
from: smtpFromName ? `"${smtpFromName}" <${smtpFromEmail}>` : smtpFromEmail,
to: rescueStatusNotificationEmail,
subject,
text: lines.join('\n'),
html: `
<p>A rescue flock was ${eventLabel}.</p>
<ul>
<li><strong>Rescue flock:</strong> ${escapedWorkspaceName}</li>
<li><strong>Verification status:</strong> ${escapedStatusLabel}</li>
<li><strong>Owner email:</strong> ${escapedOwnerEmail}</li>
<li><strong>Billing email:</strong> ${escapedBillingEmail}</li>
<li><strong>Flock ID:</strong> ${workspace.id}</li>
</ul>
`,
});
return { delivered: true };
};
const issueMagicLinkInvite = async ({
email,
name,
@@ -1006,6 +1065,12 @@ app.patch('/api/admin/rescue-workspaces/:workspaceId', requireAuth, requireSessi
return;
}
await sendRescueStatusNotification({
workspace,
ownerEmail: null,
event: 'status_changed',
});
res.json({ workspace: normalizeWorkspace(workspace) });
} catch (error) {
next(error);
@@ -1098,6 +1163,14 @@ app.post('/api/workspaces', requireAuth, requireSessionAuth, async (req: Request
owner: req.auth!.user,
});
if (workspace?.workspace_type === 'rescue') {
await sendRescueStatusNotification({
workspace,
ownerEmail: req.auth!.user.email,
event: 'created',
});
}
res.status(201).json({ workspace: normalizeWorkspace(workspace!) });
} catch (error) {
next(error);
@@ -1126,6 +1199,14 @@ app.put('/api/workspace', requireAuth, requireWriteAccess, requireWorkspaceRole(
billingPlan,
});
if (workspace?.workspace_type === 'rescue' && req.auth!.workspace.workspace_type !== 'rescue') {
await sendRescueStatusNotification({
workspace,
ownerEmail: req.auth!.user.email,
event: 'converted',
});
}
res.json({ workspace: normalizeWorkspace(workspace!) });
} catch (error) {
next(error);
+1
View File
@@ -31,6 +31,7 @@ services:
FRONTEND_URL: ${FRONTEND_URL:?set FRONTEND_URL for production}
BACKEND_URL: ${BACKEND_URL:?set BACKEND_URL for production}
ADMIN_EMAILS: ${ADMIN_EMAILS:-}
RESCUE_STATUS_NOTIFICATION_EMAIL: ${RESCUE_STATUS_NOTIFICATION_EMAIL:-appadmin@flockpal.app}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
+1
View File
@@ -30,6 +30,7 @@ services:
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000}
BACKEND_URL: ${BACKEND_URL:-http://localhost:5000}
ADMIN_EMAILS: ${ADMIN_EMAILS:-}
RESCUE_STATUS_NOTIFICATION_EMAIL: ${RESCUE_STATUS_NOTIFICATION_EMAIL:-appadmin@flockpal.app}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}