2025-11-24 01:00:50 +01:00
import { type BetterAuthOptions } from 'better-auth' ;
2026-07-10 04:22:16 +00:00
import { buildEmailBranding , buildEmailFromName , sendEmail } from '@cio/email' ;
import { resolveVerificationOrg } from './resolve-verification-org' ;
2025-11-24 01:00:50 +01:00
type EmailVerificationOptions = Parameters <
NonNullable < NonNullable < BetterAuthOptions [ 'emailVerification' ] > [ 'sendVerificationEmail' ] >
> [ 0 ];
type ChangeEmailConfirmationOptions = Parameters <
NonNullable < NonNullable < NonNullable < BetterAuthOptions [ 'user' ] > [ 'changeEmail' ] > [ 'sendChangeEmailConfirmation' ] >
> [ 0 ];
2026-07-10 04:51:30 +00:00
/**
* Email verification flow.
*
* 1. user says I want to change my email from email@old.com to email@new.com
* 2. we send an email to email@old.com asking user to verify if they really want to change their email to email@new.com `sendChangeEmailConfirmation()`. [better-auth].
* 3. user clicks the link in the email
* 4. we update the email in the database. [better-auth].
* 5. we send a confirmation email to email@new.com `sendVerificationEmail()`. [dashboard].
* 6. user clicks the link in the email
* 7. we mark email@new.com as verified. [better-auth].
*/
2026-07-10 04:22:16 +00:00
function stripTriggerParam ( url : string ) : string {
return url . replace ( 'trigger=app' , '' ). replace ( 'trigger%3Dapp' , '' );
}
2026-07-10 04:51:30 +00:00
/**
* Brands verification mail with the org from `resolveVerificationOrg` (callback URL
* host/?org=, self-hosted first org, then the user's first membership). When no
* org applies — e.g. dashboard signup before onboarding creates an org — copy
* and masthead fall back to ClassroomIO defaults.
*/
2026-07-10 04:22:16 +00:00
async function sendOrgAwareVerifyEmail ( options : {
to : string ;
verificationUrl : string ;
userId : string ;
userName? : string | null ;
newEmail? : string ;
}) {
const org = await resolveVerificationOrg ({
verificationUrl : options.verificationUrl ,
userId : options.userId
});
const orgName = org ? . name ? . trim () || 'ClassroomIO' ;
const branding = buildEmailBranding ( org ? { name : org.name , avatarUrl : org.avatarUrl , theme : org.theme } : undefined );
await sendEmail ( 'verifyEmail' , {
to : options.to ,
from : org ? . name ? buildEmailFromName ( ` ${ org . name } (via ClassroomIO.com)` ) : undefined ,
subject : org?.name ? `Confirm your email for ${ org . name } ` : undefined ,
fields : {
link : stripTriggerParam ( options . verificationUrl ),
newEmail : options.newEmail ,
userName : options.userName || undefined ,
orgName ,
branding
}
});
}
2025-11-24 01:00:50 +01:00
/**
* Sends a verification email to the user when they sign up or change their email address.
* @param options - The options for sending the verification email.
* @returns The verification email.
*/
export const sendVerificationEmail = async ( options : EmailVerificationOptions ) => {
const { user , url } = options ;
2025-12-02 09:54:38 +01:00
console . log ( '\nsendVerificationEmail' , options );
2025-11-24 01:00:50 +01:00
// The purpose of this is to prevent verification email trigger from better-auth because we already have a flow in the UI for triggering this email.
if ( ! url . includes ( 'trigger=app' ) && ! url . includes ( 'trigger%3Dapp' )) {
return ;
}
2026-07-10 04:22:16 +00:00
await sendOrgAwareVerifyEmail ({
2025-11-24 01:00:50 +01:00
to : user.email ,
2026-07-10 04:22:16 +00:00
verificationUrl : url ,
userId : user.id ,
userName : user.name
2025-11-24 01:00:50 +01:00
});
};
/**
* Sends a confirmation email to the user when they change their email address.
* @param options - The options for sending the change email confirmation.
* @returns The confirmation email.
*/
export const sendChangeEmailConfirmation = async ( options : ChangeEmailConfirmationOptions ) => {
const { user , newEmail , url } = options ;
2025-12-02 09:54:38 +01:00
console . log ( '\nsendChangeEmailConfirmation' , options );
2025-11-24 01:00:50 +01:00
2026-07-10 04:22:16 +00:00
await sendOrgAwareVerifyEmail ({
2025-11-24 01:00:50 +01:00
to : user.email ,
2026-07-10 04:22:16 +00:00
verificationUrl : url ,
userId : user.id ,
userName : user.name ,
newEmail
2025-11-24 01:00:50 +01:00
});
};