should [firstname] in code be replaced by a form variable as presumably nothing has been saved yet - if so what would the form variable be?
Nope, the user object hasn't even been constructed at that point. So you can't use user substitutions as the user doesn't exist yet (in object or in the database). You'd need to directly access the POST values, but you need to secure it with some cleaning. The below should work fine.
Code:
$_POST['email'] = '[cb:parse function="clean" method="string"][post_firstname][/cb:parse]@domain.com';
The below will also work to use PHP to grab and clean the firstname from POST.
Code:
$firstName = \CBLib\Application\Application::Input()->get( 'firstname', uniqid(), CBLib\Registry\GetterInterface::STRING );
$_POST['email'] = $firstName . '@domain.com';