Repository: LemmyNet/lemmy (language: rust)

Issue:
Resend email verification
### Requirements

- [X] Is this a feature request? For questions or discussions use https://lemmy.ml/c/lemmy_support
- [X] Did you check to see if this issue already exists?
- [X] Is this only a feature request? Do not put multiple feature requests in one issue.
- [X] Is this a backend issue? Use the [lemmy-ui](https://github.com/LemmyNet/lemmy-ui) repo for UI / frontend issues.
- [X] Do you agree to follow the rules in our [Code of Conduct](https://join-lemmy.org/docs/code_of_conduct.html)?

### Is your proposal related to a problem?

It should be possible for users to request a new email verification to be sent, eg if the previous verification failed to be delivered.

See https://github.com/LemmyNet/lemmy/issues/4562#issuecomment-2466424237



Discussion / hints:

Fix patch:
diff --git a/crates/api/src/local_user/mod.rs b/crates/api/src/local_user/mod.rs
index 15243056dc..fc2da0b2fc 100644
--- a/crates/api/src/local_user/mod.rs
+++ b/crates/api/src/local_user/mod.rs
@@ -14,6 +14,7 @@ pub mod login;
 pub mod logout;
 pub mod notifications;
 pub mod report_count;
+pub mod resend_verification_email;
 pub mod reset_password;
 pub mod save_settings;
 pub mod update_totp;
diff --git a/crates/api/src/local_user/resend_verification_email.rs b/crates/api/src/local_user/resend_verification_email.rs
new file mode 100644
index 0000000000..72122ef612
--- /dev/null
+++ b/crates/api/src/local_user/resend_verification_email.rs
@@ -0,0 +1,30 @@
+use actix_web::web::{Data, Json};
+use lemmy_api_common::{
+  context::LemmyContext,
+  person::ResendVerificationEmail,
+  utils::send_verification_email_if_required,
+  SuccessResponse,
+};
+use lemmy_db_views::structs::{LocalUserView, SiteView};
+use lemmy_utils::error::LemmyResult;
+
+pub async fn resend_verification_email(
+  data: Json<ResendVerificationEmail>,
+  context: Data<LemmyContext>,
+) -> LemmyResult<Json<SuccessResponse>> {
+  let site_view = SiteView::read_local(&mut context.pool()).await?;
+  let email = data.email.to_string();
+
+  // Fetch that email
+  let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email).await?;
+
+  send_verification_email_if_required(
+    &context,
+    &site_view.local_site,
+    &local_user_view.local_user,
+    &local_user_view.person,
+  )
+  .await?;
+
+  Ok(Json(SuccessResponse::default()))
+}
diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs
index 2aad91e0c1..895de1de97 100644
--- a/crates/api_common/src/person.rs
+++ b/crates/api_common/src/person.rs
@@ -535,3 +535,11 @@ pub struct ListMediaResponse {
 pub struct ListLoginsResponse {
   pub logins: Vec<LoginToken>,
 }
+
+#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+/// Make a request to resend your verification email.
+pub struct ResendVerificationEmail {
+  pub email: SensitiveString,
+}
diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs
index 623386c78b..a7fd73f110 100644
--- a/crates/api_common/src/utils.rs
+++ b/crates/api_common/src/utils.rs
@@ -494,6 +494,34 @@ pub async fn send_verification_email(
   send_email(&subject, new_email, &person.name, &body, settings).await
 }
 
+/// Returns true if email was sent.
+pub async fn send_verification_email_if_required(
+  context: &LemmyContext,
+  local_site: &LocalSite,
+  local_user: &LocalUser,
+  person: &Person,
+) -> LemmyResult<bool> {
+  let email = &local_user
+    .email
+    .clone()
+    .ok_or(LemmyErrorType::EmailRequired)?;
+
+  if !local_user.admin && local_site.require_email_verification && !local_user.email_verified {
+    send_verification_email(
+      local_site,
+      local_user,
+      person,
+      email,
+      &mut context.pool(),
+      context.settings(),
+    )
+    .await?;
+    Ok(true)
+  } else {
+    Ok(false)
+  }
+}
+
 pub fn local_site_rate_limit_to_rate_limit_config(
   l: &LocalSiteRateLimit,
 ) -> EnumMap<ActionType, BucketConfig> {
diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs
index a4d3c334b3..0182b9b1fe 100644
--- a/crates/api_crud/src/user/create.rs
+++ b/crates/api_crud/src/user/create.rs
@@ -14,7 +14,7 @@ use lemmy_api_common::{
     local_site_to_slur_regex,
     password_length_check,
     send_new_applicant_email_to_admins,
-    send_verification_email,
+    send_verification_email_if_required,
   },
 };
 use lemmy_db_schema::{
@@ -474,34 +474,6 @@ async fn create_local_user(
   Ok(inserted_local_user)
 }
 
-/// Returns true if email was sent.
-async fn send_verification_email_if_required(
-  context: &Data<LemmyContext>,
-  local_site: &LocalSite,
-  local_user: &LocalUser,
-  person: &Person,
-) -> LemmyResult<bool> {
-  let email = &local_user
-    .email
-    .clone()
-    .ok_or(LemmyErrorType::EmailRequired)?;
-
-  if !local_user.admin && local_site.require_email_verification && !local_user.email_verified {
-    send_verification_email(
-      local_site,
-      local_user,
-      person,
-      email,
-      &mut context.pool(),
-      context.settings(),
-    )
-    .await?;
-    Ok(true)
-  } else {
-    Ok(false)
-  }
-}
-
 fn validate_registration_answer(
   require_registration_application: bool,
   answer: &Option<String>,
diff --git a/src/api_routes_v4.rs b/src/api_routes_v4.rs
index 48710b0df4..fd92246b8d 100644
--- a/src/api_routes_v4.rs
+++ b/src/api_routes_v4.rs
@@ -44,6 +44,7 @@ use lemmy_api::{
       unread_count::unread_count,
     },
     report_count::report_count,
+    resend_verification_email::resend_verification_email,
     reset_password::reset_password,
     save_settings::save_user_settings,
     update_totp::update_totp,
@@ -312,6 +313,10 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
           .route("/totp/generate", post().to(generate_totp_secret))
           .route("/totp/update", post().to(update_totp))
           .route("/verify_email", post().to(verify_email))
+          .route(
+            "/resend_verification_email",
+            post().to(resend_verification_email),
+          )
           .route("/saved", get().to(list_person_saved)),
       )
       .service(


Test patch: