feat(broker): actually send invite email via Postmark, return emailed flag
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Broker now sends the invite email when body.email is provided and
POSTMARK_API_KEY (or RESEND_API_KEY) is configured. Returns
`emailed: boolean` so the CLI can honestly report whether the email
was sent instead of falsely claiming success on link generation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-15 01:48:53 +01:00
parent 32851419e6
commit 2f27a5eef4

View File

@@ -5132,8 +5132,40 @@ async function handleCliMeshInvite(req: IncomingMessage, slug: string, res: Serv
const baseUrl = process.env.APP_URL || "https://claudemesh.com";
const url = `${baseUrl}/i/${code}`;
writeJson(res, 200, { url, code, expires_at: expiresAt.toISOString() });
log.info("mesh-invite", { route: "POST /cli/mesh/:slug/invite", slug, code, email: body.email, latency_ms: Date.now() - started });
// If an email was provided, send the invite link via Postmark/Resend.
let emailed = false;
if (body.email) {
const apiKey = process.env.POSTMARK_API_KEY ?? process.env.RESEND_API_KEY;
const fromAddr = process.env.EMAIL_FROM ?? "noreply@claudemesh.com";
if (apiKey) {
try {
const subject = `You've been invited to the "${m.name}" mesh on claudemesh`;
const text = `You've been invited to join the "${m.name}" mesh on claudemesh.\n\nAccept the invite:\n${url}\n\nThis link expires on ${expiresAt.toISOString()}.\n\nIf you didn't expect this, ignore this email.`;
const res = process.env.POSTMARK_API_KEY
? await fetch("https://api.postmarkapp.com/email", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Postmark-Server-Token": apiKey },
body: JSON.stringify({ From: fromAddr, To: body.email, Subject: subject, TextBody: text }),
signal: AbortSignal.timeout(10_000),
})
: await fetch("https://api.resend.com/emails", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
body: JSON.stringify({ from: fromAddr, to: body.email, subject, text }),
signal: AbortSignal.timeout(10_000),
});
emailed = res.ok;
if (!res.ok) log.warn("invite email send failed", { status: res.status, body: await res.text().catch(() => "") });
} catch (e) {
log.error("invite email send error", { error: e instanceof Error ? e.message : String(e) });
}
} else {
log.warn("invite email requested but no POSTMARK_API_KEY/RESEND_API_KEY configured");
}
}
writeJson(res, 200, { url, code, expires_at: expiresAt.toISOString(), emailed });
log.info("mesh-invite", { route: "POST /cli/mesh/:slug/invite", slug, code, email: body.email, emailed, latency_ms: Date.now() - started });
} catch (e) {
log.error("mesh-invite", { error: e instanceof Error ? e.message : String(e), stack: e instanceof Error ? e.stack : undefined });
writeJson(res, 500, { error: "Failed to create invite" });