Skip to content

Commit b5df90b

Browse files
committed
fix: make public api email fromAddress dynamic by project name
1 parent f47304c commit b5df90b

3 files changed

Lines changed: 55 additions & 27 deletions

File tree

apps/public-api/src/controllers/mail.controller.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,8 @@ module.exports.sendMail = async (req, res, next) => {
303303
payload,
304304
usingByok,
305305
consumedQuotaKey,
306-
templateUsed
306+
templateUsed,
307+
projectName: project.name
307308
}, {
308309
attempts: 3,
309310
backoff: { type: 'exponential', delay: 5000 }
@@ -353,11 +354,17 @@ const resolveResendClient = async (req) => {
353354
throw err;
354355
}
355356

357+
let fromAddress = project?.resendFromEmail?.trim() || process.env.EMAIL_FROM;
358+
if (!fromAddress) {
359+
const { generateDynamicFromAddress } = require("@urbackend/common/src/utils/emailService");
360+
fromAddress = generateDynamicFromAddress(project.name);
361+
}
362+
356363
return {
357364
resend: new Resend(apiKey),
358365
apiKey,
359366
usingByok,
360-
fromAddress: project?.resendFromEmail?.trim() || process.env.EMAIL_FROM || `Notification from ${project.name} <urbackend@apps.bitbros.in>`
367+
fromAddress
361368
};
362369
};
363370

packages/common/src/queues/publicEmailQueue.js

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,23 @@ const initPublicEmailWorker = () => {
3333

3434
// Initialize Worker with Rate Limiting (10 per second to respect Resend limits)
3535
worker = new Worker('public-email-queue', async (job) => {
36-
const { projectId, payload, usingByok, consumedQuotaKey, templateUsed } = job.data;
36+
const { projectId, payload, usingByok, consumedQuotaKey, templateUsed, projectName } = job.data;
3737

3838
let clientKey = process.env.RESEND_API_KEY_2 || process.env.RESEND_API_KEY;
39-
let fromAddress = process.env.EMAIL_FROM || "urBackend <urbackend@apps.bitbros.in>";
39+
let fromAddress = process.env.EMAIL_FROM;
4040

4141
try {
42-
if (projectId && usingByok) {
43-
const project = await Project.findById(projectId).select('+resendApiKey.encrypted +resendApiKey.iv +resendApiKey.tag resendFromEmail').lean();
44-
if (project && project.resendApiKey) {
45-
const decrypted = decrypt(project.resendApiKey);
46-
if (typeof decrypted === 'string' && decrypted.trim().length > 0) {
47-
clientKey = decrypted.trim();
48-
fromAddress = project.resendFromEmail && project.resendFromEmail.trim()
49-
? project.resendFromEmail.trim()
50-
: "onboarding@resend.dev";
42+
if (projectId) {
43+
if (usingByok) {
44+
const project = await Project.findById(projectId).select('+resendApiKey.encrypted +resendApiKey.iv +resendApiKey.tag resendFromEmail').lean();
45+
if (project && project.resendApiKey) {
46+
const decrypted = decrypt(project.resendApiKey);
47+
if (typeof decrypted === 'string' && decrypted.trim().length > 0) {
48+
clientKey = decrypted.trim();
49+
fromAddress = project.resendFromEmail && project.resendFromEmail.trim()
50+
? project.resendFromEmail.trim()
51+
: "onboarding@resend.dev";
52+
}
5153
}
5254
}
5355
}
@@ -56,6 +58,15 @@ const initPublicEmailWorker = () => {
5658
// Fallback to global key
5759
}
5860

61+
if (!fromAddress) {
62+
if (projectName) {
63+
const { generateDynamicFromAddress } = require('../utils/emailService');
64+
fromAddress = generateDynamicFromAddress(projectName);
65+
} else {
66+
fromAddress = "urBackend <urbackend@apps.bitbros.in>";
67+
}
68+
}
69+
5970
if (!clientKey) {
6071
throw new Error("Resend API key is not configured.");
6172
}

packages/common/src/utils/emailService.js

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ const formatFromAddress = (email_address) => {
2626

2727
return `urBackend <${trimmed}>`;
2828
};
29+
30+
const generateDynamicFromAddress = (projectName) => {
31+
const rawPname = projectName || "urBackend";
32+
let safeEmailHandle = rawPname.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
33+
if (safeEmailHandle.length < 3) {
34+
safeEmailHandle = "urbackend";
35+
}
36+
safeEmailHandle = safeEmailHandle.substring(0, 30);
37+
const safeDisplayName = rawPname.replace(/[\r\n]/g, '').trim();
38+
const finalDisplayName = /^[a-zA-Z0-9 ]+$/.test(safeDisplayName)
39+
? safeDisplayName
40+
: `"${safeDisplayName.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
41+
return `${finalDisplayName} <${safeEmailHandle}@apps.bitbros.in>`;
42+
};
2943

3044
const defaultFromAddress = formatFromAddress(process.env.EMAIL_FROM);
3145
const replyToAddress = process.env.EMAIL_REPLY_TO?.trim() || "urbackend@apps.bitbros.in";
@@ -165,19 +179,7 @@ async function sendReleaseEmail(email, { version, title, content, changelogUrl }
165179
// FUNCTION - SEND AUTH OTP EMAIL
166180
async function sendAuthOtpEmail(email, { otp, type, pname, byokKey, byokFrom }) {
167181
const rawPname = pname || "urBackend";
168-
169-
let safeEmailHandle = rawPname.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
170-
if (safeEmailHandle.length < 3) {
171-
safeEmailHandle = "urbackend";
172-
}
173-
safeEmailHandle = safeEmailHandle.substring(0, 30);
174-
175182
const safeProjectNameHtml = escapeHtml(rawPname);
176-
177-
const safeDisplayName = rawPname.replace(/[\r\n]/g, '').trim();
178-
const finalDisplayName = /^[a-zA-Z0-9 ]+$/.test(safeDisplayName)
179-
? safeDisplayName
180-
: `"${safeDisplayName.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
181183

182184
const isVerify = type === 'verification';
183185
const subject = isVerify ? "Verify your account" : "Reset your password";
@@ -226,7 +228,7 @@ async function sendAuthOtpEmail(email, { otp, type, pname, byokKey, byokFrom })
226228
let mailClient = resend;
227229
let fromAddress = process.env.EMAIL_FROM
228230
? formatFromAddress(process.env.EMAIL_FROM)
229-
: `${finalDisplayName} <${safeEmailHandle}.urbackend@apps.bitbros.in>`;
231+
: generateDynamicFromAddress(rawPname);
230232

231233
if (byokKey) {
232234
mailClient = new Resend(byokKey);
@@ -384,4 +386,12 @@ urBackend Team`;
384386
}
385387
}
386388

387-
module.exports = { sendOtp, sendReleaseEmail, sendAuthOtpEmail, sendProRequestConfirmationEmail, sendExportReadyEmail };
389+
module.exports = {
390+
sendOtp,
391+
sendReleaseEmail,
392+
sendAuthOtpEmail,
393+
sendProRequestConfirmationEmail,
394+
sendExportReadyEmail,
395+
formatFromAddress,
396+
generateDynamicFromAddress
397+
};

0 commit comments

Comments
 (0)