Skip to content

[PB-1960]: feat/implement mail account provisioning and recovery email functionality#1019

Closed
jzunigax2 wants to merge 3 commits intomasterfrom
feat/mail-account-creation-flow
Closed

[PB-1960]: feat/implement mail account provisioning and recovery email functionality#1019
jzunigax2 wants to merge 3 commits intomasterfrom
feat/mail-account-creation-flow

Conversation

@jzunigax2
Copy link
Copy Markdown
Contributor

@jzunigax2 jzunigax2 commented Mar 31, 2026

  • Introduced Mail module with MailService and MailController for handling mail account creation.
  • Implemented MailUseCases to manage mail account provisioning and validation.
  • Updated user model to support recovery email and added necessary DTOs for mail account creation.
  • Enhanced audit logging to track mail setup actions.
  • Attempting a log in with an internxt owned email domain would promt an address search on mail-server

@jzunigax2 jzunigax2 self-assigned this Mar 31, 2026
@jzunigax2 jzunigax2 added the enhancement New feature or request label Mar 31, 2026
@jzunigax2 jzunigax2 force-pushed the feat/mail-account-creation-flow branch from 5886722 to a07353d Compare March 31, 2026 04:21
@jzunigax2 jzunigax2 force-pushed the feat/mail-account-creation-flow branch from a07353d to c0b82e5 Compare March 31, 2026 04:31
@jzunigax2 jzunigax2 force-pushed the feat/mail-account-creation-flow branch from c0b82e5 to 6452a6b Compare March 31, 2026 04:44
@jzunigax2 jzunigax2 marked this pull request as ready for review March 31, 2026 04:48
@jzunigax2 jzunigax2 requested a review from apsantiso as a code owner March 31, 2026 04:48
dto: CreateMailAccountDto,
): Promise<{ token: string; newToken: string; address: string }> {
await this.assertMailAccessEnabled(user);
this.verifyPassword(user, dto.password);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect users to send their password when creating an email address? What's the flow for this?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not, you could try to use the gateway for any check you need and create the mail directly from mail-server instead.

I guess it depends on the other comment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user uses a @inxt.com (example) to login, Drive (because it contains the auth) should ask mail to locate the uuid here by that username, and with that uuid and the password, match against drive.users. Am I wrong @jzunigax2 @apsantiso ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In consequence, we should not ask for the password here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sg-gs this is intended as a flow that runs the first time a user acceses mail. they choose their new internxt email address and must re enter their password right before submitting.

if succesful they would now use their new internxt email address to login and their original email becomes their recovery email.

however I do agree with @apsantiso there are many edge cases, I guess all email comunications would have to defaut to recovery email if available

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay then, anyway, use the gateway to check the password against the auth source of truth (right now, Drive)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, the reason this logic lives here in drive rather than mail is that at this point in the flow mail-server has zero user context, it doesn't know if the user exists, is authenticated, or has mail access. All of that lives here in drive.

The flow is:

  1. User exists and is already authed (drive validates the token)
  2. Drive checks the feature limit (mail access on their plan)
  3. Drive verifies the password re confirmation
  4. Drive calls mail-server via mail's gateway to provision the account
  5. Drive handles the auth side-effects like updating the email, issuing new tokens

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also share the token at some extend like we did with VPN @jzunigax2. Just throwing ideas tho. Not sure if that aligns with what @sg-gs thought

Comment thread src/modules/mail/mail.usecase.ts Outdated
Comment on lines +54 to +71
await this.userRepository.updateByUuid(user.uuid, {
email: fullAddress,
recoveryEmail: previousEmail,
});
} catch (error) {
await this.userRepository.updateByUuid(user.uuid, {
email: previousEmail,
recoveryEmail: null,
});
throw error;
}

const updatedUser = await this.userRepository.findByUuid(user.uuid);

const { token, newToken } =
await this.userUseCases.getAuthTokens(updatedUser);

return { token, newToken, address: fullAddress };
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to change how drive-server works and identifies users because of mail?
If the user downgrades then we would need to change the email again. I know it seems to be a product decision, but it could create strange edge cases here.

It might be worth to re-check this with product @jzunigax2 @sg-gs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At product level they want to make possible to login with your @inxt.com mail, which means we should match that, but just in case the user has mail enabled.

Copy link
Copy Markdown
Collaborator

@apsantiso apsantiso Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, what about preserving the email field as it is and store the internxt email in another field or even table (I would go for the second option to support multiple emails in the future)?

If the user wish to change their primary email they can always change the email manually. @jzunigax2 @sg-gs

The only downside I see is related to any sharing/workspace operation that requires searching by mail. We would need to change the mail search in those usecases

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, we need to think about the downgrade process. We would need to prevent new users from signing up with the recovery_email too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, we need to think about the downgrade process. We would need to prevent new users from signing up with the recovery_email too.

Isn't this already handled by the signup itself? Or am I missing something?

Ok, what about preserving the email field as it is and store the internxt email in another field or even table (I would go for the second option to support multiple emails in the future)?

If the user wish to change their primary email they can always change the email manually.

At our level (before Stalwart) I assume we should have the local-part (this naming comes from https://datatracker.ietf.org/doc/html/rfc5322) multiplexing per user on another table.

The only downside I see is related to any sharing/workspace operation that requires searching by mail. We would need to change the mail search in those usecases

Why? I mean, we will search by the local-part and from there, get the user (in case you need the search to find the user) or are you referring to something else?

Copy link
Copy Markdown
Collaborator

@apsantiso apsantiso Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? I mean, we will search by the local-part and from there, get the user (in case you need the search to find the user) or are you referring to something else?

@sg-gs I mean we should change how we search for the user in the "invite user" usecase, which uses the email address to find the user. Not a big deal as we use mainly uuids tho. If I can login with john@internxt.com too, I would expect other users to find me as john@internxt.com too.

Isn't this already handled by the signup itself? Or am I missing something?

No, the current status of the code switches the user's original email to recovery_email and the internxt's email becomes the new 'email'. That's why I jumped in, as changing the main email would lead to weird things. IMO we should store the data like this:

So when the users want to log in, we just need to search for the primary email + associated mails (local-parts, whatever we do).

Instead of switching them:

I may be missing things so correct me if I'm just pushing things blindly haha

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sg-gs I mean we should change how we search for the user in the "invite user" usecase, which uses the email address to find the user. Not a big deal as we use mainly uuids tho. If I can login with john@internxt.com too, I would expect other users to find me as john@internxt.com too.

Let's assume this happens, in both ways:

  1. An Internxt user that has multiple mail extensions is invited to an item. Receives the email, joins with a token, Drive's session is the same, it works.
    a. The internal invitation notifications will still appear as it is invited using its uuid. (which mail would have also)
  2. An Internxt User that does not have Mail --> not affected.

So I do not see any issue here

That's why I jumped in, as changing the main email would lead to weird things
This could be visual. I won't change it internally. Just do the following:

  1. User without mail -> works as always.
  2. User using a internxt mail domain -> ask Mail for the uuid, get the password on Drive with that uuid -> match password.
    And Drive would still have the same thing: email, password, uuid
    And Mail would store: uuid, mail.localParts.userId -> mail.users.uuid (FK), mail.localParts.value (here, there would be as many rows per user as different domains it can have)

…ality

- Added migration to include recovery_email field in users table.
- Introduced Mail module with MailService and MailController for handling mail account creation.
- Implemented MailUseCases to manage mail account provisioning and validation.
- Updated user model to support recovery email and added necessary DTOs for mail account creation.
- Enhanced audit logging to track mail setup actions.
@jzunigax2 jzunigax2 force-pushed the feat/mail-account-creation-flow branch from 6452a6b to dd84afb Compare April 15, 2026 09:56
- Deleted migration for recovery_email field in users table.
- Refactored MailService and AuthController to remove recovery email handling.
- Updated MailUseCases and user model to eliminate recovery email references.
- Introduced managed mail domains logic for email resolution in login process.
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
47.6% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@jzunigax2 jzunigax2 requested review from apsantiso and sg-gs April 16, 2026 12:18
@jzunigax2
Copy link
Copy Markdown
Contributor Author

@sg-gs @apsantiso addressed concerns, removed recovery mail stuff and the email replacement logic. I'll improve test coverage, but in the meantime does it look good overall?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should not be part of Drive, rather than from payments.

const userId = await this.mailService.findUserIdByAddress(email);
if (!userId) return email;

const user = await this.userUseCases.findByUuid(userId).catch(() => null);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't hide errors, log or let them propagate

private readonly mailService: MailService,
) {}

private async resolveLoginEmail(email: string): Promise<string> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of the login logic, I will add it to the auth.service, not in the controller, as this applies to the login itself, with independence of the web login or the CLI one

import { AuditLog } from '../../common/audit-logs/decorators/audit-log.decorator';
import { AuditAction } from '../../common/audit-logs/audit-logs.attributes';

@Controller('mail')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this here? Shouldn't this be on mail-server?

@jzunigax2
Copy link
Copy Markdown
Contributor Author

closing in favor of internxt/mail-server#25

@jzunigax2 jzunigax2 closed this Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request ready-for-preview

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants