Real SMTP heir notifications — no third-party signup needed
Per explicit direction to use existing VPS/Bitwarden/Gitea infrastructure instead of waiting on a Resend signup. Bitwarden's MCP unlock/list both hung (server-side issue, confirmed via direct CLI retry too — not something to keep retrying), so this used the VPS and Gitea directly. Found real, working infrastructure already in place: a documented but never-deployed falah-ibaas email connector (SMTP wrapper) at /opt/falah-ibaas/connectors/email, backed by a local Postfix relay that Ghost already uses successfully in production on the same VPS (mail__options__host=172.17.0.1:25, no auth needed internally). Built nf-mail-relay: a small HTTP wrapper (Python stdlib, no deps) around that connector, deployed as a Docker Swarm service on the existing Traefik network at https://nfmailrelay.falahos.my, bind-mounting the connector code read-only so it stays in sync with any future updates to it. Shared-secret bearer auth (X-Relay-Secret) — verified a wrong secret gets rejected with 401. notify-heirs Edge Function rewired to call this relay instead of Resend. Two real bugs found and fixed via actual testing, not code review: - The function only took memberId, but a person can belong to multiple families — .maybeSingle() against multiple trigger rows failed closed (correctly, but silently, as "not triggered"). Function and both call sites (db.js notifyHeirs, MutawalliDashboard) now require and pass familyId too, matching the same composite-key fix already applied to the trigger tables themselves. - No CORS/OPTIONS handling: a browser's preflight OPTIONS request has no body, and calling req.json() on it crashed the function before any headers were sent — surfaced in the browser as a generic "Failed to send a request" with no detail. Added an OPTIONS short-circuit and CORS headers on every response path. Verified with a real send to a live inbox through the full chain (browser -> Edge Function -> VPS relay -> Postfix -> SMTP), not just a connectivity check. Also fixed a stale e2e-trust.cjs assertion using the same instant-isVisible()-after-fixed-wait pattern already fixed elsewhere in this session — real app behavior was correct, only the test's timing assumption was wrong. e2e-per-member.cjs's heir-notification check now asserts an actual "Sent" result via the real relay instead of accepting either Sent or a not-configured failure. Full sweep: e2e-uat 32/32 (stable across 3 runs, one earlier run's failure was a one-off network blip under heavy parallel test load), e2e-fastpath 16/16, e2e-trust 12/12 (stable across 3 runs), e2e-business 10/10, e2e-digital-vehicle 10/10, e2e-property 9/9, e2e-other 4/4, e2e-info 31/31, e2e-per-member 11/11 — 165/165 total.
This commit is contained in:
@@ -107,7 +107,7 @@
|
||||
async function sendHeirNotifications() {
|
||||
notifyStatus = 'Sending…';
|
||||
try {
|
||||
await notifyHeirs(selectedMemberId);
|
||||
await notifyHeirs(selectedMemberId, familyId);
|
||||
notifyStatus = 'Sent.';
|
||||
} catch (e) {
|
||||
notifyStatus = 'Failed: ' + e.message;
|
||||
|
||||
+2
-2
@@ -273,8 +273,8 @@ export async function setMemberAttestorConfirmed(id, confirmed) {
|
||||
}
|
||||
|
||||
/** Sends the heir notification email via the notify-heirs Edge Function. */
|
||||
export async function notifyHeirs(memberId) {
|
||||
const { data, error } = await supabase.functions.invoke('notify-heirs', { body: { memberId } });
|
||||
export async function notifyHeirs(memberId, familyId) {
|
||||
const { data, error } = await supabase.functions.invoke('notify-heirs', { body: { memberId, familyId } });
|
||||
if (error) throw error;
|
||||
return data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user