From 2ff3bba79ba962d27cefa26962d71864ff7c58ed Mon Sep 17 00:00:00 2001 From: Tanish Valesha Date: Mon, 20 Apr 2026 18:58:59 +0530 Subject: [PATCH] fix dashboard shared email provider display for self-hosted env config Expose non-secret shared email metadata in project config and render it in dashboard instead of hard-coded shared sender text. --- apps/backend/src/lib/config.tsx | 3 ++ .../email-settings/domain-settings.tsx | 30 +++++++++++++++---- .../[projectId]/emails/page-client.tsx | 25 +++++++++++----- .../apps/implementations/admin-app-impl.ts | 7 +++-- .../lib/stack-app/project-configs/index.ts | 3 ++ 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/apps/backend/src/lib/config.tsx b/apps/backend/src/lib/config.tsx index 078dd9be83..035d95eee0 100644 --- a/apps/backend/src/lib/config.tsx +++ b/apps/backend/src/lib/config.tsx @@ -1165,6 +1165,9 @@ export const renderedOrganizationConfigToProjectCrud = (renderedConfig: Complete email_config: renderedConfig.emails.server.isShared ? { type: 'shared', + host: getEnvVariable("STACK_EMAIL_HOST"), + port: parseInt(getEnvVariable("STACK_EMAIL_PORT")), + sender_email: getEnvVariable("STACK_EMAIL_SENDER"), } : renderedConfig.emails.server.provider === "managed" ? { type: 'standard', host: "smtp.resend.com", 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 2ab1557af7..5ba28a7792 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 @@ -34,7 +34,7 @@ type ServerFieldConfig = { }; const SERVER_TYPE_LABELS: Record = { - shared: "Shared (noreply@stackframe.co)", + shared: "Shared (environment-configured)", managed: "Managed (via managed domain setup)", resend: "Resend", standard: "Custom SMTP", @@ -87,9 +87,16 @@ function getServerTypeFromConfig(config: CompleteConfig["emails"]["server"]): Se return "standard"; } -function getFormValuesFromConfig(config: CompleteConfig["emails"]["server"], projectName: string): Record { +function getFormValuesFromConfig( + config: CompleteConfig["emails"]["server"], + projectName: string, + sharedSenderEmail: string | null, +): Record { if (config.isShared) { - return { senderEmail: "noreply@stackframe.co", senderName: projectName }; + return { + senderEmail: sharedSenderEmail ?? "Configured via STACK_EMAIL_SENDER", + senderName: projectName, + }; } if (config.provider === "managed") { const senderEmail = config.managedSubdomain && config.managedSenderLocalPart @@ -357,12 +364,21 @@ export function DomainSettings() { const stackAdminApp = useAdminApp(); const project = stackAdminApp.useProject(); const emailConfig = project.useConfig().emails.server; + const sharedSenderEmail = project.config.emailConfig?.type === "shared" + && "senderEmail" in project.config.emailConfig + && typeof project.config.emailConfig.senderEmail === "string" + ? project.config.emailConfig.senderEmail + : null; const updateConfig = useUpdateConfig(); const { toast } = useToast(); const isEmulator = getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true"; const savedServerType = getServerTypeFromConfig(emailConfig); - const savedValues = getFormValuesFromConfig(emailConfig, project.displayName); + const savedValues = getFormValuesFromConfig( + emailConfig, + project.displayName, + sharedSenderEmail, + ); const [serverType, setServerType] = useState(savedServerType); const [formValues, setFormValues] = useState>(savedValues); @@ -541,8 +557,10 @@ export function DomainSettings() {
{isShared ? ( - - noreply@stackframe.co + + + {sharedSenderEmail ?? "Configured via STACK_EMAIL_SENDER"} + ) : serverType === "managed" ? ( 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..35bc8e0e92 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 @@ -1,6 +1,7 @@ "use client"; import { TeamMemberSearchTable } from "@/components/data-table/team-member-search-table"; +import { DesignAnalyticsCard } from "@/components/design-components"; import { FormDialog } from "@/components/form-dialog"; import { InputField, SelectField, TextAreaField } from "@/components/form-fields"; import { ActionDialog, Alert, AlertDescription, AlertTitle, Button, DataTable, DataTableColumnHeader, DataTableViewOptions, SimpleTooltip, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Typography, useToast } from "@/components/ui"; @@ -19,7 +20,6 @@ import * as yup from "yup"; import { AppEnabledGuard } from "../app-enabled-guard"; import { PageLayout } from "../page-layout"; import { useAdminApp } from "../use-admin-app"; -import { DesignAnalyticsCard } from "@/components/design-components"; // Section header with icon following design guide function SectionHeader({ icon: Icon, title }: { icon: ElementType, title: string }) { @@ -59,6 +59,11 @@ export default function PageClient() { const stackAdminApp = useAdminApp(); const project = stackAdminApp.useProject(); const emailConfig = project.useConfig().emails.server; + const sharedSenderEmail = project.config.emailConfig?.type === "shared" + && "senderEmail" in project.config.emailConfig + && typeof project.config.emailConfig.senderEmail === "string" + ? project.config.emailConfig.senderEmail + : null; const isLocalEmulator = getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true"; return ( @@ -82,7 +87,7 @@ export default function PageClient() { {isLocalEmulator && } {/* Email Server Card */} - + {/* Email Log Card */} @@ -130,16 +135,22 @@ function EmulatorModeCard() { ); } -function EmailServerCard({ emailConfig }: { emailConfig: CompleteConfig['emails']['server'] }) { +function EmailServerCard({ + emailConfig, + sharedSenderEmail, +}: { + emailConfig: CompleteConfig['emails']['server'], + sharedSenderEmail: string | null, +}) { const isLocalEmulator = getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true"; const serverType = emailConfig.isShared - ? 'Shared' + ? 'Shared (environment-configured)' : emailConfig.provider === 'managed' ? 'Managed By Stack Auth' : (emailConfig.provider === 'resend' ? 'Resend' : 'Custom SMTP'); const senderEmail = emailConfig.isShared - ? 'noreply@stackframe.co' + ? (sharedSenderEmail ?? 'Configured via STACK_EMAIL_SENDER') : emailConfig.provider === 'managed' && emailConfig.managedSubdomain && emailConfig.managedSenderLocalPart ? `${emailConfig.managedSenderLocalPart}@${emailConfig.managedSubdomain}` : emailConfig.senderEmail; @@ -207,7 +218,7 @@ function EmailServerCard({ emailConfig }: { emailConfig: CompleteConfig['emails'
{serverType} {emailConfig.isShared && ( - + )}
@@ -820,7 +831,7 @@ function EditEmailServerDialog(props: { name="type" control={form.control} options={[ - { label: "Shared (noreply@stackframe.co)", value: 'shared' }, + { label: "Shared (environment-configured)", 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' }, diff --git a/packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts b/packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts index 3238b7194b..eae7ae97df 100644 --- a/packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts +++ b/packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts @@ -22,7 +22,7 @@ import { InternalApiKey, InternalApiKeyBase, InternalApiKeyBaseCrudRead, Interna import { AdminProjectPermission, AdminProjectPermissionDefinition, AdminProjectPermissionDefinitionCreateOptions, AdminProjectPermissionDefinitionUpdateOptions, AdminTeamPermission, AdminTeamPermissionDefinition, AdminTeamPermissionDefinitionCreateOptions, AdminTeamPermissionDefinitionUpdateOptions, adminProjectPermissionDefinitionCreateOptionsToCrud, adminProjectPermissionDefinitionUpdateOptionsToCrud, adminTeamPermissionDefinitionCreateOptionsToCrud, adminTeamPermissionDefinitionUpdateOptionsToCrud } from "../../permissions"; import { AdminOwnedProject, AdminProject, AdminProjectUpdateOptions, PushConfigOptions, adminProjectUpdateOptionsToCrud } from "../../projects"; import type { AdminSessionReplay, AdminSessionReplayChunk, ListSessionReplayChunksOptions, ListSessionReplayChunksResult, ListSessionReplaysOptions, ListSessionReplaysResult, SessionReplayAllEventsResult } from "../../session-replays"; -import { ManagedEmailProviderListItem, ManagedEmailProviderSetupResult, ManagedEmailProviderStatus, EmailOutboxUpdateOptions, StackAdminApp, StackAdminAppConstructorOptions } from "../interfaces/admin-app"; +import { EmailOutboxUpdateOptions, ManagedEmailProviderListItem, ManagedEmailProviderSetupResult, ManagedEmailProviderStatus, StackAdminApp, StackAdminAppConstructorOptions } from "../interfaces/admin-app"; import { clientVersion, createCache, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getDefaultSecretServerKey, getDefaultSuperSecretAdminKey, resolveApiUrls, resolveConstructorOptions } from "./common"; import { _StackServerAppImplIncomplete } from "./server-app-impl"; @@ -208,7 +208,10 @@ export class _StackAdminAppImplIncomplete