import { yupResolver } from '@hookform/resolvers/yup' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import AlertError from 'components/ui/AlertError' import { InlineLink } from 'components/ui/InlineLink' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Eye, EyeOff } from 'lucide-react' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, Card, CardContent, CardFooter, cn, Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, Input_Shadcn_, PrePostTab, Switch, } from 'ui' import { Admonition, PageSection, PageSectionContent } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import * as yup from 'yup' import { urlRegex } from '../Auth.constants' import { defaultDisabledSmtpFormValues } from './SmtpForm.constants' import { generateFormValues, isSmtpEnabled } from './SmtpForm.utils' interface SmtpFormValues { SMTP_ADMIN_EMAIL?: string SMTP_SENDER_NAME?: string SMTP_HOST?: string SMTP_PORT?: number SMTP_MAX_FREQUENCY?: number SMTP_USER?: string SMTP_PASS?: string ENABLE_SMTP: boolean } export const SmtpForm = () => { const { ref: projectRef } = useParams() const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useAuthConfigUpdateMutation() const [enableSmtp, setEnableSmtp] = useState(false) const [hidden, setHidden] = useState(true) const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) const smtpSchema = yup.object({ SMTP_ADMIN_EMAIL: yup .string() .trim() .when('ENABLE_SMTP', { is: true, then: (schema) => schema.email('Must be a valid email').required('Sender email address is required'), otherwise: (schema) => schema, }), SMTP_SENDER_NAME: yup .string() .trim() .when('ENABLE_SMTP', { is: true, then: (schema) => schema.required('Sender name is required'), otherwise: (schema) => schema, }), SMTP_HOST: yup .string() .trim() .when('ENABLE_SMTP', { is: true, then: (schema) => schema .matches(urlRegex({ excludeSimpleDomains: false }), 'Must be a valid URL or IP address') .required('Host URL is required.'), otherwise: (schema) => schema, }), SMTP_PORT: yup.number().when('ENABLE_SMTP', { is: true, then: (schema) => schema .required('Port number is required.') .min(1, 'Must be a valid port number more than 0') .max(65535, 'Must be a valid port number no more than 65535'), otherwise: (schema) => schema, }), SMTP_MAX_FREQUENCY: yup.number().when('ENABLE_SMTP', { is: true, then: (schema) => schema .required('Rate limit is required.') .min(1, 'Must be more than 0') .max(32767, 'Must not be more than 32,767 an hour'), otherwise: (schema) => schema, }), SMTP_USER: yup .string() .trim() .when('ENABLE_SMTP', { is: true, then: (schema) => schema.required('SMTP Username is required'), otherwise: (schema) => schema, }), SMTP_PASS: yup.string().trim(), ENABLE_SMTP: yup.boolean().required(), }) const form = useForm({ resolver: yupResolver(smtpSchema), defaultValues: { SMTP_ADMIN_EMAIL: '', SMTP_SENDER_NAME: '', SMTP_HOST: '', SMTP_PORT: undefined, SMTP_MAX_FREQUENCY: undefined, SMTP_USER: '', SMTP_PASS: '', ENABLE_SMTP: false, }, }) // Update form values when auth config is loaded useEffect(() => { if (authConfig) { const formValues = generateFormValues(authConfig) // Convert SMTP_PORT from string to number if it exists if (formValues.SMTP_PORT) { formValues.SMTP_PORT = Number(formValues.SMTP_PORT) as any } form.reset({ ...formValues, ENABLE_SMTP: isSmtpEnabled(authConfig), } as SmtpFormValues) setEnableSmtp(isSmtpEnabled(authConfig)) } }, [authConfig, form]) // Update enableSmtp state when the form field changes useEffect(() => { const subscription = form.watch((value, { name }) => { if (name === 'ENABLE_SMTP') { setEnableSmtp(value.ENABLE_SMTP as boolean) } }) return () => subscription.unsubscribe() }, [form]) const onSubmit = (values: SmtpFormValues) => { const { ENABLE_SMTP, ...rest } = values const basePayload = ENABLE_SMTP ? rest : defaultDisabledSmtpFormValues // When enabling SMTP, set RATE_LIMIT_EMAIL_SENT to 30 // When disabling, backend will handle resetting to default const isEnablingSmtp = ENABLE_SMTP && !isSmtpEnabled(authConfig) const payload = { ...basePayload, ...(isEnablingSmtp && { RATE_LIMIT_EMAIL_SENT: 30 }), } // Format payload: Convert port to string if (payload.SMTP_PORT) { payload.SMTP_PORT = payload.SMTP_PORT.toString() as any } // the SMTP_PASS is write-only, it's never shown. If we don't delete it from the payload, it will replace the // previously saved value with an empty one if (payload.SMTP_PASS === '') { delete payload.SMTP_PASS } updateAuthConfig( { projectRef: projectRef!, config: payload as any }, { onError: (error) => { toast.error(`Failed to update settings: ${error.message}`) }, onSuccess: () => { setHidden(true) toast.success('Successfully updated settings') }, } ) } if (isError) { return ( ) } if (!canReadConfig) { return ( ) } return (
( Emails will be sent using your custom SMTP provider. Email rate limits can be adjusted{' '} here .

} >
)} /> {enableSmtp && !isSmtpEnabled(form.getValues() as any) && ( )}
{enableSmtp && ( <>

Sender details

Configure the sender information for your emails.

( )} /> ( )} />

SMTP provider settings

Your SMTP credentials will always be encrypted in our database.

( )} /> {form.watch('SMTP_HOST')?.endsWith('.gmail.com') && ( )} ( Port used by your SMTP server. Common ports include 465 and 587. Avoid using port 25 as it is often blocked by providers to curb spam. } > field.onChange(Number(e.target.value))} placeholder="587" disabled={!canUpdateConfig} /> )} /> ( field.onChange(Number(e.target.value))} disabled={!canUpdateConfig} /> )} /> ( )} /> ( setHidden(!hidden)} icon={hidden ? : } /> } > )} />
)} {form.formState.isDirty && (

{enableSmtp ? ( <> Rate limit for sending emails will be increased to 30 and{' '} can be adjusted {' '} after enabling custom SMTP ) : ( 'Rate limit for sending emails will be reduced to 2 after disabling custom SMTP' )}

)}
{form.formState.isDirty && ( )}
) }