diff --git a/apps/backend/src/app/api/latest/internal/config/route.test.tsx b/apps/backend/src/app/api/latest/internal/config/route.test.tsx new file mode 100644 index 0000000000..baa990a69a --- /dev/null +++ b/apps/backend/src/app/api/latest/internal/config/route.test.tsx @@ -0,0 +1,34 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { getConfigForInternalConfigResponse } from "./route"; + +describe("getConfigForInternalConfigResponse", () => { + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it("injects the configured sender email for shared email server configs", () => { + vi.stubEnv("STACK_EMAIL_SENDER", "noreply@lolcalho.st"); + const config = { + emails: { + server: { + isShared: true, + senderEmail: undefined, + }, + }, + untouched: "value", + }; + + const result = getConfigForInternalConfigResponse(config); + + expect(result).toEqual({ + emails: { + server: { + isShared: true, + senderEmail: "noreply@lolcalho.st", + }, + }, + untouched: "value", + }); + expect(config.emails.server.senderEmail).toBeUndefined(); + }); +}); diff --git a/apps/backend/src/app/api/latest/internal/config/route.tsx b/apps/backend/src/app/api/latest/internal/config/route.tsx index c7f1207f6c..1df4e905e7 100644 --- a/apps/backend/src/app/api/latest/internal/config/route.tsx +++ b/apps/backend/src/app/api/latest/internal/config/route.tsx @@ -1,5 +1,23 @@ import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { adaptSchema, adminAuthTypeSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; +import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; + +export function getConfigForInternalConfigResponse(config: T): T { + if (!config.emails.server.isShared) { + return config; + } + + return { + ...config, + emails: { + ...config.emails, + server: { + ...config.emails.server, + senderEmail: getEnvVariable("STACK_EMAIL_SENDER"), + }, + }, + }; +} export const GET = createSmartRouteHandler({ metadata: { @@ -22,11 +40,13 @@ export const GET = createSmartRouteHandler({ }).defined(), }), handler: async (req) => { + const config = getConfigForInternalConfigResponse(req.auth.tenancy.config); + return { statusCode: 200, bodyType: "json", body: { - config_string: JSON.stringify(req.auth.tenancy.config), + config_string: JSON.stringify(config), }, }; }, diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-settings/domain-settings.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-settings/domain-settings.tsx index cafd59cf82..4bae11cd95 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-settings/domain-settings.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-settings/domain-settings.tsx @@ -41,6 +41,20 @@ type ServerType = "shared" | "managed" | "resend" | "standard"; type ManagedDomainStatus = "pending_dns" | "pending_verification" | "verified" | "applied" | "failed"; +const DEFAULT_SHARED_SENDER_EMAIL = "noreply@stackframe.co"; + +type ServerFieldConfig = { + label: string, + key: string, + type: "text" | "email" | "number" | "password", +}; + +const SERVER_TYPE_LABELS: Record, string> = { + managed: "Managed (via managed domain setup)", + resend: "Resend", + standard: "Custom SMTP", +}; + type ManagedDomain = { domainId: string, subdomain: string, @@ -57,6 +71,10 @@ type SetupState = { status: ManagedDomainStatus, }; +function getSharedServerTypeLabel(senderEmail: string | undefined): string { + return `Shared (${senderEmail || DEFAULT_SHARED_SENDER_EMAIL})`; +} + const MANAGED_DOMAIN_STATUS_LABELS: Record = { pending_dns: "Waiting for DNS", pending_verification: "Verifying…", @@ -82,7 +100,7 @@ function getServerTypeFromConfig(config: CompleteConfig["emails"]["server"]): Se function getFormValuesFromConfig(config: CompleteConfig["emails"]["server"], projectName: string): Record { if (config.isShared) { - return { senderEmail: "noreply@stackframe.co", senderName: projectName }; + return { senderEmail: config.senderEmail ?? "", senderName: projectName }; } if (config.provider === "managed") { const senderEmail = config.managedSubdomain && config.managedSenderLocalPart diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx index 46dfc22f10..0bc59a6516 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx @@ -21,6 +21,8 @@ import { PageLayout } from "../page-layout"; import { useAdminApp } from "../use-admin-app"; import { DesignAnalyticsCard } from "@/components/design-components"; +const DEFAULT_SHARED_SENDER_EMAIL = "noreply@stackframe.co"; + // Section header with icon following design guide function SectionHeader({ icon: Icon, title }: { icon: ElementType, title: string }) { return ( @@ -139,7 +141,7 @@ function EmailServerCard({ emailConfig }: { emailConfig: CompleteConfig['emails' : (emailConfig.provider === 'resend' ? 'Resend' : 'Custom SMTP'); const senderEmail = emailConfig.isShared - ? 'noreply@stackframe.co' + ? (emailConfig.senderEmail ?? DEFAULT_SHARED_SENDER_EMAIL) : emailConfig.provider === 'managed' && emailConfig.managedSubdomain && emailConfig.managedSenderLocalPart ? `${emailConfig.managedSenderLocalPart}@${emailConfig.managedSubdomain}` : emailConfig.senderEmail; @@ -602,7 +604,7 @@ const getDefaultValues = (emailConfig: CompleteConfig['emails']['server'] | unde if (!emailConfig) { return { type: 'shared', senderName: project.displayName } as const; } else if (emailConfig.isShared) { - return { type: 'shared' } as const; + return { type: 'shared', senderEmail: emailConfig.senderEmail } as const; } else if (emailConfig.provider === 'resend') { return { type: 'resend', @@ -820,7 +822,7 @@ function EditEmailServerDialog(props: { name="type" control={form.control} options={[ - { label: "Shared (noreply@stackframe.co)", value: 'shared' }, + { label: `Shared (${defaultValues.type === "shared" ? (defaultValues.senderEmail ?? DEFAULT_SHARED_SENDER_EMAIL) : DEFAULT_SHARED_SENDER_EMAIL})`, value: 'shared' }, { label: "Managed (via managed domain setup)", value: 'managed' }, { label: "Resend (your own email address)", value: 'resend' }, { label: "Custom SMTP server (your own email address)", value: 'standard' },