Skip to content

Commit 00f269e

Browse files
committed
added envvar for personal emails sent, added isHosted gate
1 parent b9192c6 commit 00f269e

4 files changed

Lines changed: 73 additions & 9 deletions

File tree

apps/sim/lib/auth/auth.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,18 @@ import {
4848
isAuthDisabled,
4949
isBillingEnabled,
5050
isEmailVerificationEnabled,
51+
isHosted,
5152
isRegistrationDisabled,
5253
} from '@/lib/core/config/feature-flags'
5354
import { getBaseUrl } from '@/lib/core/utils/urls'
5455
import { sendEmail } from '@/lib/messaging/email/mailer'
55-
import { getFromEmailAddress } from '@/lib/messaging/email/utils'
56+
import { getFromEmailAddress, getPersonalEmailFrom } from '@/lib/messaging/email/utils'
5657
import { quickValidateEmail } from '@/lib/messaging/email/validation'
5758
import { createAnonymousSession, ensureAnonymousUserExists } from './anonymous'
5859
import { SSO_TRUSTED_PROVIDERS } from './sso/constants'
5960

6061
const logger = createLogger('Auth')
6162

62-
// Only initialize Stripe if the key is provided
63-
// This allows local development without a Stripe account
6463
const validStripeKey = env.STRIPE_SECRET_KEY
6564

6665
let stripeClient = null
@@ -106,22 +105,21 @@ export const auth = betterAuth({
106105
})
107106
}
108107

109-
// Send welcome email
110-
if (user.email) {
108+
if (isHosted && user.email && user.emailVerified) {
111109
try {
112-
const { sendEmail } = await import('@/lib/messaging/email/mailer')
113110
const html = await renderWelcomeEmail(user.name || undefined)
111+
const { from, replyTo } = getPersonalEmailFrom()
114112

115113
await sendEmail({
116114
to: user.email,
117115
subject: getEmailSubject('welcome'),
118116
html,
119-
from: 'Emir from Sim <emir@sim.ai>',
120-
replyTo: 'emir@sim.ai',
117+
from,
118+
replyTo,
121119
emailType: 'transactional',
122120
})
123121

124-
logger.info('[databaseHooks.user.create.after] Welcome email sent', {
122+
logger.info('[databaseHooks.user.create.after] Welcome email sent to OAuth user', {
125123
userId: user.id,
126124
})
127125
} catch (error) {
@@ -321,6 +319,35 @@ export const auth = betterAuth({
321319
],
322320
},
323321
},
322+
emailVerification: {
323+
autoSignInAfterVerification: true,
324+
afterEmailVerification: async (user) => {
325+
if (isHosted && user.email) {
326+
try {
327+
const html = await renderWelcomeEmail(user.name || undefined)
328+
const { from, replyTo } = getPersonalEmailFrom()
329+
330+
await sendEmail({
331+
to: user.email,
332+
subject: getEmailSubject('welcome'),
333+
html,
334+
from,
335+
replyTo,
336+
emailType: 'transactional',
337+
})
338+
339+
logger.info('[emailVerification.afterEmailVerification] Welcome email sent', {
340+
userId: user.id,
341+
})
342+
} catch (error) {
343+
logger.error('[emailVerification.afterEmailVerification] Failed to send welcome email', {
344+
userId: user.id,
345+
error,
346+
})
347+
}
348+
}
349+
},
350+
},
324351
emailAndPassword: {
325352
enabled: true,
326353
requireEmailVerification: isEmailVerificationEnabled,

apps/sim/lib/billing/webhooks/invoices.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { setUsageLimitForCredits } from '@/lib/billing/credits/purchase'
1717
import { requireStripeClient } from '@/lib/billing/stripe-client'
1818
import { getBaseUrl } from '@/lib/core/utils/urls'
1919
import { sendEmail } from '@/lib/messaging/email/mailer'
20+
import { getPersonalEmailFrom } from '@/lib/messaging/email/utils'
2021
import { quickValidateEmail } from '@/lib/messaging/email/validation'
2122

2223
const logger = createLogger('StripeInvoiceWebhooks')
@@ -138,6 +139,7 @@ async function getPaymentMethodDetails(
138139

139140
/**
140141
* Send payment failure notification emails to affected users
142+
* Note: This is only called when billing is enabled (Stripe plugin loaded)
141143
*/
142144
async function sendPaymentFailureEmails(
143145
sub: { plan: string | null; referenceId: string },
@@ -202,10 +204,13 @@ async function sendPaymentFailureEmails(
202204
})
203205
)
204206

207+
const { from, replyTo } = getPersonalEmailFrom()
205208
await sendEmail({
206209
to: userToNotify.email,
207210
subject: 'Payment Failed - Action Required',
208211
html: emailHtml,
212+
from,
213+
replyTo,
209214
emailType: 'transactional',
210215
})
211216

apps/sim/lib/core/config/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const env = createEnv({
6060
EMAIL_VERIFICATION_ENABLED: z.boolean().optional(), // Enable email verification for user registration and login (defaults to false)
6161
RESEND_API_KEY: z.string().min(1).optional(), // Resend API key for transactional emails
6262
FROM_EMAIL_ADDRESS: z.string().min(1).optional(), // Complete from address (e.g., "Sim <noreply@domain.com>" or "noreply@domain.com")
63+
PERSONAL_EMAIL_FROM: z.string().min(1).optional(), // From address for personalized emails (e.g., "Emir from Sim <emir@sim.ai>")
6364
EMAIL_DOMAIN: z.string().min(1).optional(), // Domain for sending emails (fallback when FROM_EMAIL_ADDRESS not set)
6465
AZURE_ACS_CONNECTION_STRING: z.string().optional(), // Azure Communication Services connection string
6566

apps/sim/lib/messaging/email/utils.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,34 @@ export function getFromEmailAddress(): string {
1111
// Fallback to constructing from EMAIL_DOMAIN
1212
return `noreply@${env.EMAIL_DOMAIN || getEmailDomain()}`
1313
}
14+
15+
/**
16+
* Extract the email address from a "Name <email>" formatted string"
17+
*/
18+
export function extractEmailFromAddress(fromAddress: string): string | undefined {
19+
const match = fromAddress.match(/<([^>]+)>/)
20+
if (match) {
21+
return match[1]
22+
}
23+
if (fromAddress.includes('@') && !fromAddress.includes('<')) {
24+
return fromAddress.trim()
25+
}
26+
return undefined
27+
}
28+
29+
/**
30+
* Get the personal email from address and reply-to
31+
*/
32+
export function getPersonalEmailFrom(): { from: string; replyTo: string | undefined } {
33+
const personalFrom = env.PERSONAL_EMAIL_FROM
34+
if (personalFrom) {
35+
return {
36+
from: personalFrom,
37+
replyTo: extractEmailFromAddress(personalFrom),
38+
}
39+
}
40+
return {
41+
from: getFromEmailAddress(),
42+
replyTo: undefined,
43+
}
44+
}

0 commit comments

Comments
 (0)