Candidate Email Verification Flow#
This document explains the candidate email verification flow in the StudentHub backend, focusing on the POST /v1/auth/resend-verification-email endpoint. It details routing, controller logic, data flow, verification code generation, email sending, response handling, and security checks to help developers debug cases where users see “code sent” in the UI but never receive an email.
Routing & Controller#
Endpoint and Controller#
- Endpoint:
POST /v1/auth/resend-verification-email - Controller:
candidate\modules\v1\controllers\AuthController - Action:
actionResendVerificationEmail - File path:
candidate/modules/v1/controllers/AuthController.php
The related endpoint for sending the verification code during signup is:
- Endpoint:
POST /v1/auth/register - Controller: Same as above
- Action:
actionSignup
Route Definition#
The route is defined in candidate/config/main.php using a Yii2 REST URL rule:
[
'class' => 'yii\rest\UrlRule',
'controller' => 'v1/auth',
'pluralize' => false,
'patterns' => [
// ...
'POST resend-verification-email' => 'resend-verification-email',
'POST register' => 'signup',
// ...
]
],
Data Flow#
Request Data#
POST /v1/auth/resend-verification-email expects a JSON body with:
email(string, required): The candidate's email address.token(string, optional): Recaptcha token (currently not enforced; see Security section).
Example request:
{
"email": "user@example.com",
"token": "recaptcha-token-optional"
}
Controller Logic#
- The controller retrieves the
emailandtokenfrom the request body. - (Recaptcha verification is present but commented out.)
- It looks up the candidate by email.
- If the candidate is not found, it returns an error.
- If the candidate's email is already verified, it returns an error.
- If a verification email was sent less than 1 minute ago, it returns an error with a message indicating the wait time.
- If eligible, it calls
$candidate->sendVerificationEmail()to generate a new code and send the email. - Returns a JSON response indicating success or error.
Verification Code Generation & Persistence#
- The verification code is generated by
Candidate::generateAuthKey(), which sets thecandidate_auth_keyfield to a random string. - The code is persisted in the
candidate_auth_keycolumn of theCandidatemodel. - The timestamp for the last sent email is updated in
candidate_limit_email.
These operations occur in Candidate::sendVerificationEmail() in common/models/Candidate.php.
See code generation and persistence
Email Sending Logic#
Where the Email is Sent#
The email is sent in Candidate::sendVerificationEmail() (common/models/Candidate.php):
public function sendVerificationEmail() {
$this->generateAuthKey();
$this->candidate_limit_email = new Expression('NOW()');
$this->save(false);
$email = $this->candidate_new_email ?: $this->candidate_email;
$ml = new MailLog();
$ml->to = $email;
$ml->from = \Yii::$app->params['supportEmail'];
$ml->subject = 'Please confirm your email address';
if (!$ml->save()) {
Yii::error('Failed to save mail log :' . print_r($ml->errors, true));
}
$mailer = Yii::$app->mailer->compose(
['html' => 'candidate/verify-email-html', 'text' => 'candidate/verify-email-text'],
['candidate' => $this]
)
->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->params['appName']])
->setTo($email)
->setSubject('Please confirm your email address');
if(\Yii::$app->params['elasticMailIpPool']) {
$mailer->setHeader ("poolName", \Yii::$app->params['elasticMailIpPool']);
}
try {
return $mailer->send();
} catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) {
Yii::error("Failed to send email: " . $e->getMessage());
} catch (\Exception $e) {
Yii::error("An error occurred: " . $e->getMessage());
}
}
Failure Handling#
- If saving the mail log fails, the error is logged.
- If sending the email fails (due to transport or other exceptions), the error is logged via
Yii::error, but the exception is not propagated. - The method does not return
falseor throw on failure; it only logs the error.
No queue/job is used for email sending; it is sent synchronously via Yii::$app->mailer.
Response Handling#
The controller action always returns a JSON response. If errors occur (already verified, too soon, or candidate not found), it returns an error response with a code and message. If no errors occur, it returns success with a generic message:
return [
'operation' => 'success',
'message' => Yii::t('candidate', 'Please click on the link sent to you by email to verify your account'),
];
Important: The controller does not check the result of sendVerificationEmail(); it assumes success if no exception is thrown. If email sending fails silently (e.g., SMTP issues), the user still sees “code sent” in the UI, but no email is delivered. Errors are only logged on the server.
Security / Recaptcha#
There is commented-out code for recaptcha verification in both actionResendVerificationEmail and actionSignup. The code expects a token parameter and would verify it using Yii::$app->reCaptcha->verify($token). Currently, this check is disabled (commented out), so no recaptcha or anti-bot protection is enforced at this endpoint.
Debugging: Why “Code Sent” But No Email?#
- The endpoint always returns success if the candidate exists, is not verified, and is not throttled, regardless of whether the email was actually sent.
- Email sending failures (e.g., SMTP issues, misconfiguration, blacklisting) are only logged on the server and not surfaced to the client.
- To debug, check the application logs for errors from
sendVerificationEmail()and ensure the mailer is correctly configured. - If recaptcha is required in the future, uncomment and enforce the recaptcha check in the controller.
Related Signup Flow#
The signup endpoint (POST /v1/auth/register) uses the same logic: after creating the candidate, it calls $model->signup(), which in turn calls sendVerificationEmail() if the email is not already verified.
Summary Diagram#
References: