Keep a published rule when some invitation emails already went out, and only set Reply-To when the inviter address is a mailbox.

This commit is contained in:
adilallo committed 2026-11-06 09:22:06 -07:00
1 parent 3c984e00c5
commit ab6a0c807d
7 files changed
+161 -10

No files matched your search

+3 -1
View File
@@ -8,6 +8,7 @@ import {
import { getSmtpUrl } from "./env";
import {
buildRuleStakeholderInviteMail,
stakeholderInviteReplyTo,
type RuleStakeholderInviteMailInput,
} from "./ruleStakeholderInviteMail";
@@ -102,12 +103,13 @@ export async function sendRuleStakeholderInviteEmail(
input: RuleStakeholderInviteMailInput,
): Promise<void> {
const { subject, text, html } = buildRuleStakeholderInviteMail(input);
const replyTo = stakeholderInviteReplyTo(input.inviterEmail);
await sendHtmlMail({
to: input.to,
subject,
text,
html,
replyTo: input.inviterEmail.trim().toLowerCase(),
...(replyTo ? { replyTo } : {}),
devLog: `[dev] Rule stakeholder invite (${input.communityName}) for ${input.to}: ${input.verifyUrl}`,
});
}
+23 -1
View File
@@ -8,6 +8,28 @@ import {
export const RULE_STAKEHOLDER_INVITE_LOGO_PATH = getAssetPath(ASSETS.LOGO_MARK);
/** Letter name when neither the inviter nor the rule owner has an email. */
export const RULE_STAKEHOLDER_INVITER_FALLBACK = "Someone from this community";
const MAILBOX_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
/** Reply-To only when the inviter value is a mailbox. */
export function stakeholderInviteReplyTo(
inviterEmail: string,
): string | undefined {
const mailbox = inviterEmail.trim().toLowerCase();
if (!MAILBOX_PATTERN.test(mailbox)) return undefined;
return mailbox;
}
/** Email addresses stay lowercase; any other label is kept for the letter. */
export function stakeholderInviteInviterLabel(inviterEmail: string): string {
const replyTo = stakeholderInviteReplyTo(inviterEmail);
if (replyTo) return replyTo;
const trimmed = inviterEmail.trim();
return trimmed.length > 0 ? trimmed : RULE_STAKEHOLDER_INVITER_FALLBACK;
}
const PREVIEW_TEXT =
"It covers how they talk, who belongs, how they decide and what happens when they disagree.";
@@ -44,7 +66,7 @@ export function buildRuleStakeholderInviteMail(
input: RuleStakeholderInviteMailInput,
): RuleStakeholderInviteMailContent {
const communityName = input.communityName.trim() || "A community";
const inviterEmail = input.inviterEmail.trim().toLowerCase();
const inviterEmail = stakeholderInviteInviterLabel(input.inviterEmail);
const origin = input.origin.replace(/\/$/, "");
const verifyUrl = input.verifyUrl;
const copyUrl = ruleStakeholderInviteCopyUrl(verifyUrl);