diff --git a/models/error.go b/models/error.go
index b365a67b73..db0fce0ce1 100644
--- a/models/error.go
+++ b/models/error.go
@@ -123,21 +123,6 @@ func (err ErrUserNotExist) Error() string {
 	return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID)
 }
 
-// ErrUserRedirectNotExist represents a "UserRedirectNotExist" kind of error.
-type ErrUserRedirectNotExist struct {
-	Name string
-}
-
-// IsErrUserRedirectNotExist check if an error is an ErrUserRedirectNotExist.
-func IsErrUserRedirectNotExist(err error) bool {
-	_, ok := err.(ErrUserRedirectNotExist)
-	return ok
-}
-
-func (err ErrUserRedirectNotExist) Error() string {
-	return fmt.Sprintf("user redirect does not exist [name: %s]", err.Name)
-}
-
 // ErrUserProhibitLogin represents a "ErrUserProhibitLogin" kind of error.
 type ErrUserProhibitLogin struct {
 	UID  int64
@@ -170,66 +155,6 @@ func (err ErrUserInactive) Error() string {
 	return fmt.Sprintf("user is inactive [uid: %d, name: %s]", err.UID, err.Name)
 }
 
-// ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error.
-type ErrEmailAlreadyUsed struct {
-	Email string
-}
-
-// IsErrEmailAlreadyUsed checks if an error is a ErrEmailAlreadyUsed.
-func IsErrEmailAlreadyUsed(err error) bool {
-	_, ok := err.(ErrEmailAlreadyUsed)
-	return ok
-}
-
-func (err ErrEmailAlreadyUsed) Error() string {
-	return fmt.Sprintf("e-mail already in use [email: %s]", err.Email)
-}
-
-// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
-type ErrEmailInvalid struct {
-	Email string
-}
-
-// IsErrEmailInvalid checks if an error is an ErrEmailInvalid
-func IsErrEmailInvalid(err error) bool {
-	_, ok := err.(ErrEmailInvalid)
-	return ok
-}
-
-func (err ErrEmailInvalid) Error() string {
-	return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
-}
-
-// ErrEmailAddressNotExist email address not exist
-type ErrEmailAddressNotExist struct {
-	Email string
-}
-
-// IsErrEmailAddressNotExist checks if an error is an ErrEmailAddressNotExist
-func IsErrEmailAddressNotExist(err error) bool {
-	_, ok := err.(ErrEmailAddressNotExist)
-	return ok
-}
-
-func (err ErrEmailAddressNotExist) Error() string {
-	return fmt.Sprintf("Email address does not exist [email: %s]", err.Email)
-}
-
-// ErrPrimaryEmailCannotDelete primary email address cannot be deleted
-type ErrPrimaryEmailCannotDelete struct {
-	Email string
-}
-
-// IsErrPrimaryEmailCannotDelete checks if an error is an ErrPrimaryEmailCannotDelete
-func IsErrPrimaryEmailCannotDelete(err error) bool {
-	_, ok := err.(ErrPrimaryEmailCannotDelete)
-	return ok
-}
-
-func (err ErrPrimaryEmailCannotDelete) Error() string {
-	return fmt.Sprintf("Primary email address cannot be deleted [email: %s]", err.Email)
-}
-
 // ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
 type ErrOpenIDAlreadyUsed struct {
 	OpenID string
diff --git a/models/gpg_key.go b/models/gpg_key.go
index a62ed61966..357dc2b964 100644
--- a/models/gpg_key.go
+++ b/models/gpg_key.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models/db"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/timeutil"
 
@@ -36,7 +37,7 @@ type GPGKey struct {
 	ExpiredUnix       timeutil.TimeStamp
 	AddedUnix         timeutil.TimeStamp
 	SubsKey           []*GPGKey `xorm:"-"`
-	Emails            []*EmailAddress
+	Emails            []*user_model.EmailAddress
 	Verified          bool `xorm:"NOT NULL DEFAULT false"`
 	CanSign           bool
 	CanEncryptComms   bool
@@ -148,12 +149,12 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity, verified bool) (*GPGKey, erro
 	}
 
 	// Check emails
-	userEmails, err := GetEmailAddresses(ownerID)
+	userEmails, err := user_model.GetEmailAddresses(ownerID)
 	if err != nil {
 		return nil, err
 	}
 
-	emails := make([]*EmailAddress, 0, len(e.Identities))
+	emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
 	for _, ident := range e.Identities {
 		if ident.Revocation != nil {
 			continue
@@ -242,7 +243,7 @@ func DeleteGPGKey(doer *User, id int64) (err error) {
 
 func checkKeyEmails(email string, keys ...*GPGKey) (bool, string) {
 	uid := int64(0)
-	var userEmails []*EmailAddress
+	var userEmails []*user_model.EmailAddress
 	var user *User
 	for _, key := range keys {
 		for _, e := range key.Emails {
@@ -252,7 +253,7 @@ func checkKeyEmails(email string, keys ...*GPGKey) (bool, string) {
 		}
 		if key.Verified && key.OwnerID != 0 {
 			if uid != key.OwnerID {
-				userEmails, _ = GetEmailAddresses(key.OwnerID)
+				userEmails, _ = user_model.GetEmailAddresses(key.OwnerID)
 				uid = key.OwnerID
 				user = &User{ID: uid}
 				_, _ = GetUser(user)
diff --git a/models/gpg_key_commit_verification.go b/models/gpg_key_commit_verification.go
index f508303a09..6da65d3697 100644
--- a/models/gpg_key_commit_verification.go
+++ b/models/gpg_key_commit_verification.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -167,7 +168,7 @@ func ParseCommitWithSignature(c *git.Commit) *CommitVerification {
 			}
 		}
 
-		committerEmailAddresses, _ := GetEmailAddresses(committer.ID)
+		committerEmailAddresses, _ := user_model.GetEmailAddresses(committer.ID)
 		activated := false
 		for _, e := range committerEmailAddresses {
 			if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
diff --git a/models/org.go b/models/org.go
index c6d2562f15..1840cae9fa 100644
--- a/models/org.go
+++ b/models/org.go
@@ -11,6 +11,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/unit"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/storage"
@@ -163,25 +164,25 @@ func CreateOrganization(org, owner *User) (err error) {
 	org.NumMembers = 1
 	org.Type = UserTypeOrganization
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
+		return err
+	}
+	defer committer.Close()
+
+	if err = user_model.DeleteUserRedirect(ctx, org.Name); err != nil {
 		return err
 	}
 
-	if err = deleteUserRedirect(sess, org.Name); err != nil {
-		return err
-	}
-
-	if _, err = sess.Insert(org); err != nil {
+	if err = db.Insert(ctx, org); err != nil {
 		return fmt.Errorf("insert organization: %v", err)
 	}
-	if err = org.generateRandomAvatar(sess); err != nil {
+	if err = org.generateRandomAvatar(db.GetEngine(ctx)); err != nil {
 		return fmt.Errorf("generate random avatar: %v", err)
 	}
 
 	// Add initial creator to organization and owner team.
-	if _, err = sess.Insert(&OrgUser{
+	if err = db.Insert(ctx, &OrgUser{
 		UID:   owner.ID,
 		OrgID: org.ID,
 	}); err != nil {
@@ -198,7 +199,7 @@ func CreateOrganization(org, owner *User) (err error) {
 		IncludesAllRepositories: true,
 		CanCreateOrgRepo:        true,
 	}
-	if _, err = sess.Insert(t); err != nil {
+	if err = db.Insert(ctx, t); err != nil {
 		return fmt.Errorf("insert owner team: %v", err)
 	}
 
@@ -212,14 +213,11 @@ func CreateOrganization(org, owner *User) (err error) {
 		})
 	}
 
-	if _, err = sess.Insert(&units); err != nil {
-		if err := sess.Rollback(); err != nil {
-			log.Error("CreateOrganization: sess.Rollback: %v", err)
-		}
+	if err = db.Insert(ctx, &units); err != nil {
 		return err
 	}
 
-	if _, err = sess.Insert(&TeamUser{
+	if err = db.Insert(ctx, &TeamUser{
 		UID:    owner.ID,
 		OrgID:  org.ID,
 		TeamID: t.ID,
@@ -227,7 +225,7 @@ func CreateOrganization(org, owner *User) (err error) {
 		return fmt.Errorf("insert team-user relation: %v", err)
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
 
 // GetOrgByName returns organization by given name.
diff --git a/models/ssh_key_principals.go b/models/ssh_key_principals.go
index 44b2ee0bb4..50456b72db 100644
--- a/models/ssh_key_principals.go
+++ b/models/ssh_key_principals.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/setting"
 )
 
@@ -88,7 +89,7 @@ func CheckPrincipalKeyString(user *User, content string) (_ string, err error) {
 		case "anything":
 			return content, nil
 		case "email":
-			emails, err := GetEmailAddresses(user.ID)
+			emails, err := user_model.GetEmailAddresses(user.ID)
 			if err != nil {
 				return "", err
 			}
diff --git a/models/user.go b/models/user.go
index 13347a46b7..12035dbe42 100644
--- a/models/user.go
+++ b/models/user.go
@@ -23,6 +23,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/login"
 	"code.gitea.io/gitea/models/unit"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
@@ -77,9 +78,6 @@ const (
 )
 
 var (
-	// ErrEmailNotActivated e-mail address has not been activated error
-	ErrEmailNotActivated = errors.New("E-mail address has not been activated")
-
 	// ErrUserNameIllegal user name contains illegal characters error
 	ErrUserNameIllegal = errors.New("User name contains illegal characters")
 
@@ -890,14 +888,15 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
 		u.Visibility = overwriteDefault[0].Visibility
 	}
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
+
+	sess := db.GetEngine(ctx)
 
 	// validate data
-
 	if err := validateUser(u); err != nil {
 		return err
 	}
@@ -909,11 +908,13 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
 		return ErrUserAlreadyExist{u.Name}
 	}
 
-	isExist, err = isEmailUsed(sess, u.Email)
+	isExist, err = user_model.IsEmailUsed(ctx, u.Email)
 	if err != nil {
 		return err
 	} else if isExist {
-		return ErrEmailAlreadyUsed{u.Email}
+		return user_model.ErrEmailAlreadyUsed{
+			Email: u.Email,
+		}
 	}
 
 	// prepare for database
@@ -929,16 +930,16 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
 
 	// save changes to database
 
-	if err = deleteUserRedirect(sess, u.Name); err != nil {
+	if err = user_model.DeleteUserRedirect(ctx, u.Name); err != nil {
 		return err
 	}
 
-	if _, err = sess.Insert(u); err != nil {
+	if err = db.Insert(ctx, u); err != nil {
 		return err
 	}
 
 	// insert email address
-	if _, err := sess.Insert(&EmailAddress{
+	if err := db.Insert(ctx, &user_model.EmailAddress{
 		UID:         u.ID,
 		Email:       u.Email,
 		LowerEmail:  strings.ToLower(u.Email),
@@ -948,7 +949,7 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
 		return err
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
 
 func countUsers(e db.Engine) int64 {
@@ -998,7 +999,7 @@ func VerifyUserActiveCode(code string) (user *User) {
 }
 
 // VerifyActiveEmailCode verifies active email code when active account
-func VerifyActiveEmailCode(code, email string) *EmailAddress {
+func VerifyActiveEmailCode(code, email string) *user_model.EmailAddress {
 	minutes := setting.Service.ActiveCodeLives
 
 	if user := getVerifyUser(code); user != nil {
@@ -1007,7 +1008,7 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress {
 		data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
 
 		if base.VerifyTimeLimitCode(data, minutes, prefix) {
-			emailAddress := &EmailAddress{UID: user.ID, Email: email}
+			emailAddress := &user_model.EmailAddress{UID: user.ID, Email: email}
 			if has, _ := db.GetEngine(db.DefaultContext).Get(emailAddress); has {
 				return emailAddress
 			}
@@ -1023,11 +1024,12 @@ func ChangeUserName(u *User, newUserName string) (err error) {
 		return err
 	}
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	isExist, err := isUserExist(sess, 0, newUserName)
 	if err != nil {
@@ -1045,11 +1047,11 @@ func ChangeUserName(u *User, newUserName string) (err error) {
 		return fmt.Errorf("Rename user directory: %v", err)
 	}
 
-	if err = newUserRedirect(sess, u.ID, oldUserName, newUserName); err != nil {
+	if err = user_model.NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil {
 		return err
 	}
 
-	if err = sess.Commit(); err != nil {
+	if err = committer.Commit(); err != nil {
 		if err2 := util.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
 			log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2)
 			return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2)
@@ -1071,7 +1073,9 @@ func checkDupEmail(e db.Engine, u *User) error {
 	if err != nil {
 		return err
 	} else if has {
-		return ErrEmailAlreadyUsed{u.Email}
+		return user_model.ErrEmailAlreadyUsed{
+			Email: u.Email,
+		}
 	}
 	return nil
 }
@@ -1083,7 +1087,7 @@ func validateUser(u *User) error {
 	}
 
 	u.Email = strings.ToLower(u.Email)
-	return ValidateEmail(u.Email)
+	return user_model.ValidateEmail(u.Email)
 }
 
 func updateUser(e db.Engine, u *User) error {
@@ -1211,7 +1215,7 @@ func deleteUser(e db.Engine, u *User) error {
 		&Follow{FollowID: u.ID},
 		&Action{UserID: u.ID},
 		&IssueUser{UID: u.ID},
-		&EmailAddress{UID: u.ID},
+		&user_model.EmailAddress{UID: u.ID},
 		&UserOpenID{UID: u.ID},
 		&Reaction{UserID: u.ID},
 		&TeamUser{UID: u.ID},
@@ -1370,7 +1374,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) (err erro
 
 	_, err = db.GetEngine(db.DefaultContext).
 		Where("is_activated = ?", false).
-		Delete(new(EmailAddress))
+		Delete(new(user_model.EmailAddress))
 	return err
 }
 
@@ -1576,7 +1580,7 @@ func GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
 	}
 
 	// Otherwise, check in alternative list for activated email addresses
-	emailAddress := &EmailAddress{Email: email, IsActivated: true}
+	emailAddress := &user_model.EmailAddress{Email: email, IsActivated: true}
 	has, err = db.GetEngine(ctx).Get(emailAddress)
 	if err != nil {
 		return nil, err
diff --git a/models/user/email_address.go b/models/user/email_address.go
new file mode 100644
index 0000000000..74fb71d454
--- /dev/null
+++ b/models/user/email_address.go
@@ -0,0 +1,269 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/mail"
+	"strings"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/setting"
+
+	"xorm.io/builder"
+)
+
+var (
+	// ErrEmailNotActivated e-mail address has not been activated error
+	ErrEmailNotActivated = errors.New("E-mail address has not been activated")
+)
+
+// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
+type ErrEmailInvalid struct {
+	Email string
+}
+
+// IsErrEmailInvalid checks if an error is an ErrEmailInvalid
+func IsErrEmailInvalid(err error) bool {
+	_, ok := err.(ErrEmailInvalid)
+	return ok
+}
+
+func (err ErrEmailInvalid) Error() string {
+	return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
+}
+
+// ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error.
+type ErrEmailAlreadyUsed struct {
+	Email string
+}
+
+// IsErrEmailAlreadyUsed checks if an error is a ErrEmailAlreadyUsed.
+func IsErrEmailAlreadyUsed(err error) bool {
+	_, ok := err.(ErrEmailAlreadyUsed)
+	return ok
+}
+
+func (err ErrEmailAlreadyUsed) Error() string {
+	return fmt.Sprintf("e-mail already in use [email: %s]", err.Email)
+}
+
+// ErrEmailAddressNotExist email address not exist
+type ErrEmailAddressNotExist struct {
+	Email string
+}
+
+// IsErrEmailAddressNotExist checks if an error is an ErrEmailAddressNotExist
+func IsErrEmailAddressNotExist(err error) bool {
+	_, ok := err.(ErrEmailAddressNotExist)
+	return ok
+}
+
+func (err ErrEmailAddressNotExist) Error() string {
+	return fmt.Sprintf("Email address does not exist [email: %s]", err.Email)
+}
+
+// ErrPrimaryEmailCannotDelete primary email address cannot be deleted
+type ErrPrimaryEmailCannotDelete struct {
+	Email string
+}
+
+// IsErrPrimaryEmailCannotDelete checks if an error is an ErrPrimaryEmailCannotDelete
+func IsErrPrimaryEmailCannotDelete(err error) bool {
+	_, ok := err.(ErrPrimaryEmailCannotDelete)
+	return ok
+}
+
+func (err ErrPrimaryEmailCannotDelete) Error() string {
+	return fmt.Sprintf("Primary email address cannot be deleted [email: %s]", err.Email)
+}
+
+// EmailAddress is the list of all email addresses of a user. It also contains the
+// primary email address which is saved in user table.
+type EmailAddress struct {
+	ID          int64  `xorm:"pk autoincr"`
+	UID         int64  `xorm:"INDEX NOT NULL"`
+	Email       string `xorm:"UNIQUE NOT NULL"`
+	LowerEmail  string `xorm:"UNIQUE NOT NULL"`
+	IsActivated bool
+	IsPrimary   bool `xorm:"DEFAULT(false) NOT NULL"`
+}
+
+func init() {
+	db.RegisterModel(new(EmailAddress))
+}
+
+// BeforeInsert will be invoked by XORM before inserting a record
+func (email *EmailAddress) BeforeInsert() {
+	if email.LowerEmail == "" {
+		email.LowerEmail = strings.ToLower(email.Email)
+	}
+}
+
+// ValidateEmail check if email is a allowed address
+func ValidateEmail(email string) error {
+	if len(email) == 0 {
+		return nil
+	}
+
+	if _, err := mail.ParseAddress(email); err != nil {
+		return ErrEmailInvalid{email}
+	}
+
+	// TODO: add an email allow/block list
+
+	return nil
+}
+
+// GetEmailAddresses returns all email addresses belongs to given user.
+func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
+	emails := make([]*EmailAddress, 0, 5)
+	if err := db.GetEngine(db.DefaultContext).
+		Where("uid=?", uid).
+		Asc("id").
+		Find(&emails); err != nil {
+		return nil, err
+	}
+	return emails, nil
+}
+
+// GetEmailAddressByID gets a user's email address by ID
+func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) {
+	// User ID is required for security reasons
+	email := &EmailAddress{UID: uid}
+	if has, err := db.GetEngine(db.DefaultContext).ID(id).Get(email); err != nil {
+		return nil, err
+	} else if !has {
+		return nil, nil
+	}
+	return email, nil
+}
+
+// IsEmailActive check if email is activated with a different emailID
+func IsEmailActive(ctx context.Context, email string, excludeEmailID int64) (bool, error) {
+	if len(email) == 0 {
+		return true, nil
+	}
+
+	// Can't filter by boolean field unless it's explicit
+	cond := builder.NewCond()
+	cond = cond.And(builder.Eq{"lower_email": strings.ToLower(email)}, builder.Neq{"id": excludeEmailID})
+	if setting.Service.RegisterEmailConfirm {
+		// Inactive (unvalidated) addresses don't count as active if email validation is required
+		cond = cond.And(builder.Eq{"is_activated": true})
+	}
+
+	var em EmailAddress
+	if has, err := db.GetEngine(ctx).Where(cond).Get(&em); has || err != nil {
+		if has {
+			log.Info("isEmailActive(%q, %d) found duplicate in email ID %d", email, excludeEmailID, em.ID)
+		}
+		return has, err
+	}
+
+	return false, nil
+}
+
+// IsEmailUsed returns true if the email has been used.
+func IsEmailUsed(ctx context.Context, email string) (bool, error) {
+	if len(email) == 0 {
+		return true, nil
+	}
+
+	return db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
+}
+
+func addEmailAddress(ctx context.Context, email *EmailAddress) error {
+	email.Email = strings.TrimSpace(email.Email)
+	used, err := IsEmailUsed(ctx, email.Email)
+	if err != nil {
+		return err
+	} else if used {
+		return ErrEmailAlreadyUsed{email.Email}
+	}
+
+	if err = ValidateEmail(email.Email); err != nil {
+		return err
+	}
+
+	return db.Insert(ctx, email)
+}
+
+// AddEmailAddress adds an email address to given user.
+func AddEmailAddress(email *EmailAddress) error {
+	return addEmailAddress(db.DefaultContext, email)
+}
+
+// AddEmailAddresses adds an email address to given user.
+func AddEmailAddresses(emails []*EmailAddress) error {
+	if len(emails) == 0 {
+		return nil
+	}
+
+	// Check if any of them has been used
+	for i := range emails {
+		emails[i].Email = strings.TrimSpace(emails[i].Email)
+		used, err := IsEmailUsed(db.DefaultContext, emails[i].Email)
+		if err != nil {
+			return err
+		} else if used {
+			return ErrEmailAlreadyUsed{emails[i].Email}
+		}
+		if err = ValidateEmail(emails[i].Email); err != nil {
+			return err
+		}
+	}
+
+	if err := db.Insert(db.DefaultContext, emails); err != nil {
+		return fmt.Errorf("Insert: %v", err)
+	}
+
+	return nil
+}
+
+// DeleteEmailAddress deletes an email address of given user.
+func DeleteEmailAddress(email *EmailAddress) (err error) {
+	if email.IsPrimary {
+		return ErrPrimaryEmailCannotDelete{Email: email.Email}
+	}
+
+	var deleted int64
+	// ask to check UID
+	address := EmailAddress{
+		UID: email.UID,
+	}
+	if email.ID > 0 {
+		deleted, err = db.GetEngine(db.DefaultContext).ID(email.ID).Delete(&address)
+	} else {
+		if email.Email != "" && email.LowerEmail == "" {
+			email.LowerEmail = strings.ToLower(email.Email)
+		}
+		deleted, err = db.GetEngine(db.DefaultContext).
+			Where("lower_email=?", email.LowerEmail).
+			Delete(&address)
+	}
+
+	if err != nil {
+		return err
+	} else if deleted != 1 {
+		return ErrEmailAddressNotExist{Email: email.Email}
+	}
+	return nil
+}
+
+// DeleteEmailAddresses deletes multiple email addresses
+func DeleteEmailAddresses(emails []*EmailAddress) (err error) {
+	for i := range emails {
+		if err = DeleteEmailAddress(emails[i]); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go
new file mode 100644
index 0000000000..5ed0bf8884
--- /dev/null
+++ b/models/user/email_address_test.go
@@ -0,0 +1,131 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+	"testing"
+
+	"code.gitea.io/gitea/models/db"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetEmailAddresses(t *testing.T) {
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	emails, _ := GetEmailAddresses(int64(1))
+	if assert.Len(t, emails, 3) {
+		assert.True(t, emails[0].IsPrimary)
+		assert.True(t, emails[2].IsActivated)
+		assert.False(t, emails[2].IsPrimary)
+	}
+
+	emails, _ = GetEmailAddresses(int64(2))
+	if assert.Len(t, emails, 2) {
+		assert.True(t, emails[0].IsPrimary)
+		assert.True(t, emails[0].IsActivated)
+	}
+}
+
+func TestIsEmailUsed(t *testing.T) {
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	isExist, _ := IsEmailUsed(db.DefaultContext, "")
+	assert.True(t, isExist)
+	isExist, _ = IsEmailUsed(db.DefaultContext, "user11@example.com")
+	assert.True(t, isExist)
+	isExist, _ = IsEmailUsed(db.DefaultContext, "user1234567890@example.com")
+	assert.False(t, isExist)
+}
+
+func TestAddEmailAddress(t *testing.T) {
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	assert.NoError(t, AddEmailAddress(&EmailAddress{
+		Email:       "user1234567890@example.com",
+		LowerEmail:  "user1234567890@example.com",
+		IsPrimary:   true,
+		IsActivated: true,
+	}))
+
+	// ErrEmailAlreadyUsed
+	err := AddEmailAddress(&EmailAddress{
+		Email:      "user1234567890@example.com",
+		LowerEmail: "user1234567890@example.com",
+	})
+	assert.Error(t, err)
+	assert.True(t, IsErrEmailAlreadyUsed(err))
+}
+
+func TestAddEmailAddresses(t *testing.T) {
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	// insert multiple email address
+	emails := make([]*EmailAddress, 2)
+	emails[0] = &EmailAddress{
+		Email:       "user1234@example.com",
+		LowerEmail:  "user1234@example.com",
+		IsActivated: true,
+	}
+	emails[1] = &EmailAddress{
+		Email:       "user5678@example.com",
+		LowerEmail:  "user5678@example.com",
+		IsActivated: true,
+	}
+	assert.NoError(t, AddEmailAddresses(emails))
+
+	// ErrEmailAlreadyUsed
+	err := AddEmailAddresses(emails)
+	assert.Error(t, err)
+	assert.True(t, IsErrEmailAlreadyUsed(err))
+}
+
+func TestDeleteEmailAddress(t *testing.T) {
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	assert.NoError(t, DeleteEmailAddress(&EmailAddress{
+		UID:        int64(1),
+		ID:         int64(33),
+		Email:      "user1-2@example.com",
+		LowerEmail: "user1-2@example.com",
+	}))
+
+	assert.NoError(t, DeleteEmailAddress(&EmailAddress{
+		UID:        int64(1),
+		Email:      "user1-3@example.com",
+		LowerEmail: "user1-3@example.com",
+	}))
+
+	// Email address does not exist
+	err := DeleteEmailAddress(&EmailAddress{
+		UID:        int64(1),
+		Email:      "user1234567890@example.com",
+		LowerEmail: "user1234567890@example.com",
+	})
+	assert.Error(t, err)
+}
+
+func TestDeleteEmailAddresses(t *testing.T) {
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	// delete multiple email address
+	emails := make([]*EmailAddress, 2)
+	emails[0] = &EmailAddress{
+		UID:        int64(2),
+		ID:         int64(3),
+		Email:      "user2@example.com",
+		LowerEmail: "user2@example.com",
+	}
+	emails[1] = &EmailAddress{
+		UID:        int64(2),
+		Email:      "user2-2@example.com",
+		LowerEmail: "user2-2@example.com",
+	}
+	assert.NoError(t, DeleteEmailAddresses(emails))
+
+	// ErrEmailAlreadyUsed
+	err := DeleteEmailAddresses(emails)
+	assert.Error(t, err)
+}
diff --git a/models/user/main_test.go b/models/user/main_test.go
new file mode 100644
index 0000000000..2999c4c81d
--- /dev/null
+++ b/models/user/main_test.go
@@ -0,0 +1,19 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models/db"
+)
+
+func TestMain(m *testing.M) {
+	db.MainTest(m, filepath.Join("..", ".."),
+		"email_address.yml",
+		"user_redirect.yml",
+	)
+}
diff --git a/models/user/redirect.go b/models/user/redirect.go
new file mode 100644
index 0000000000..49370218db
--- /dev/null
+++ b/models/user/redirect.go
@@ -0,0 +1,79 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"code.gitea.io/gitea/models/db"
+)
+
+// ErrUserRedirectNotExist represents a "UserRedirectNotExist" kind of error.
+type ErrUserRedirectNotExist struct {
+	Name string
+}
+
+// IsErrUserRedirectNotExist check if an error is an ErrUserRedirectNotExist.
+func IsErrUserRedirectNotExist(err error) bool {
+	_, ok := err.(ErrUserRedirectNotExist)
+	return ok
+}
+
+func (err ErrUserRedirectNotExist) Error() string {
+	return fmt.Sprintf("user redirect does not exist [name: %s]", err.Name)
+}
+
+// Redirect represents that a user name should be redirected to another
+type Redirect struct {
+	ID             int64  `xorm:"pk autoincr"`
+	LowerName      string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+	RedirectUserID int64  // userID to redirect to
+}
+
+// TableName provides the real table name
+func (Redirect) TableName() string {
+	return "user_redirect"
+}
+
+func init() {
+	db.RegisterModel(new(Redirect))
+}
+
+// LookupUserRedirect look up userID if a user has a redirect name
+func LookupUserRedirect(userName string) (int64, error) {
+	userName = strings.ToLower(userName)
+	redirect := &Redirect{LowerName: userName}
+	if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil {
+		return 0, err
+	} else if !has {
+		return 0, ErrUserRedirectNotExist{Name: userName}
+	}
+	return redirect.RedirectUserID, nil
+}
+
+// NewUserRedirect create a new user redirect
+func NewUserRedirect(ctx context.Context, ID int64, oldUserName, newUserName string) error {
+	oldUserName = strings.ToLower(oldUserName)
+	newUserName = strings.ToLower(newUserName)
+
+	if err := DeleteUserRedirect(ctx, newUserName); err != nil {
+		return err
+	}
+
+	return db.Insert(ctx, &Redirect{
+		LowerName:      oldUserName,
+		RedirectUserID: ID,
+	})
+}
+
+// DeleteUserRedirect delete any redirect from the specified user name to
+// anything else
+func DeleteUserRedirect(ctx context.Context, userName string) error {
+	userName = strings.ToLower(userName)
+	_, err := db.GetEngine(ctx).Delete(&Redirect{LowerName: userName})
+	return err
+}
diff --git a/models/user/redirect_test.go b/models/user/redirect_test.go
new file mode 100644
index 0000000000..b33c42cf3d
--- /dev/null
+++ b/models/user/redirect_test.go
@@ -0,0 +1,23 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+	"testing"
+
+	"code.gitea.io/gitea/models/db"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLookupUserRedirect(t *testing.T) {
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	userID, err := LookupUserRedirect("olduser1")
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, userID)
+
+	_, err = LookupUserRedirect("doesnotexist")
+	assert.True(t, IsErrUserRedirectNotExist(err))
+}
diff --git a/models/user_mail.go b/models/user_email.go
similarity index 50%
rename from models/user_mail.go
rename to models/user_email.go
index caa931788d..7de577bc2c 100644
--- a/models/user_mail.go
+++ b/models/user_email.go
@@ -1,5 +1,4 @@
-// Copyright 2016 The Gogs Authors. All rights reserved.
-// Copyright 2020 The Gitea Authors. All rights reserved.
+// Copyright 2021 The Gitea Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
@@ -7,179 +6,28 @@ package models
 
 import (
 	"fmt"
-	"net/mail"
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/setting"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/util"
-
 	"xorm.io/builder"
 )
 
-// EmailAddress is the list of all email addresses of a user. It also contains the
-// primary email address which is saved in user table.
-type EmailAddress struct {
-	ID          int64  `xorm:"pk autoincr"`
-	UID         int64  `xorm:"INDEX NOT NULL"`
-	Email       string `xorm:"UNIQUE NOT NULL"`
-	LowerEmail  string `xorm:"UNIQUE NOT NULL"`
-	IsActivated bool
-	IsPrimary   bool `xorm:"DEFAULT(false) NOT NULL"`
-}
-
-func init() {
-	db.RegisterModel(new(EmailAddress))
-}
-
-// BeforeInsert will be invoked by XORM before inserting a record
-func (email *EmailAddress) BeforeInsert() {
-	if email.LowerEmail == "" {
-		email.LowerEmail = strings.ToLower(email.Email)
-	}
-}
-
-// ValidateEmail check if email is a allowed address
-func ValidateEmail(email string) error {
-	if len(email) == 0 {
-		return nil
-	}
-
-	if _, err := mail.ParseAddress(email); err != nil {
-		return ErrEmailInvalid{email}
-	}
-
-	// TODO: add an email allow/block list
-
-	return nil
-}
-
-// GetEmailAddresses returns all email addresses belongs to given user.
-func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
-	emails := make([]*EmailAddress, 0, 5)
-	if err := db.GetEngine(db.DefaultContext).
-		Where("uid=?", uid).
-		Asc("id").
-		Find(&emails); err != nil {
-		return nil, err
-	}
-	return emails, nil
-}
-
-// GetEmailAddressByID gets a user's email address by ID
-func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) {
-	// User ID is required for security reasons
-	email := &EmailAddress{UID: uid}
-	if has, err := db.GetEngine(db.DefaultContext).ID(id).Get(email); err != nil {
-		return nil, err
-	} else if !has {
-		return nil, nil
-	}
-	return email, nil
-}
-
-// isEmailActive check if email is activated with a different emailID
-func isEmailActive(e db.Engine, email string, excludeEmailID int64) (bool, error) {
-	if len(email) == 0 {
-		return true, nil
-	}
-
-	// Can't filter by boolean field unless it's explicit
-	cond := builder.NewCond()
-	cond = cond.And(builder.Eq{"lower_email": strings.ToLower(email)}, builder.Neq{"id": excludeEmailID})
-	if setting.Service.RegisterEmailConfirm {
-		// Inactive (unvalidated) addresses don't count as active if email validation is required
-		cond = cond.And(builder.Eq{"is_activated": true})
-	}
-
-	var em EmailAddress
-	if has, err := e.Where(cond).Get(&em); has || err != nil {
-		if has {
-			log.Info("isEmailActive(%q, %d) found duplicate in email ID %d", email, excludeEmailID, em.ID)
-		}
-		return has, err
-	}
-
-	return false, nil
-}
-
-func isEmailUsed(e db.Engine, email string) (bool, error) {
-	if len(email) == 0 {
-		return true, nil
-	}
-
-	return e.Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
-}
-
-// IsEmailUsed returns true if the email has been used.
-func IsEmailUsed(email string) (bool, error) {
-	return isEmailUsed(db.GetEngine(db.DefaultContext), email)
-}
-
-func addEmailAddress(e db.Engine, email *EmailAddress) error {
-	email.Email = strings.TrimSpace(email.Email)
-	used, err := isEmailUsed(e, email.Email)
-	if err != nil {
-		return err
-	} else if used {
-		return ErrEmailAlreadyUsed{email.Email}
-	}
-
-	if err = ValidateEmail(email.Email); err != nil {
-		return err
-	}
-
-	_, err = e.Insert(email)
-	return err
-}
-
-// AddEmailAddress adds an email address to given user.
-func AddEmailAddress(email *EmailAddress) error {
-	return addEmailAddress(db.GetEngine(db.DefaultContext), email)
-}
-
-// AddEmailAddresses adds an email address to given user.
-func AddEmailAddresses(emails []*EmailAddress) error {
-	if len(emails) == 0 {
-		return nil
-	}
-
-	// Check if any of them has been used
-	for i := range emails {
-		emails[i].Email = strings.TrimSpace(emails[i].Email)
-		used, err := IsEmailUsed(emails[i].Email)
-		if err != nil {
-			return err
-		} else if used {
-			return ErrEmailAlreadyUsed{emails[i].Email}
-		}
-		if err = ValidateEmail(emails[i].Email); err != nil {
-			return err
-		}
-	}
-
-	if _, err := db.GetEngine(db.DefaultContext).Insert(emails); err != nil {
-		return fmt.Errorf("Insert: %v", err)
-	}
-
-	return nil
-}
-
-// Activate activates the email address to given user.
-func (email *EmailAddress) Activate() error {
+// ActivateEmail activates the email address to given user.
+func ActivateEmail(email *user_model.EmailAddress) error {
 	sess := db.NewSession(db.DefaultContext)
 	defer sess.Close()
 	if err := sess.Begin(); err != nil {
 		return err
 	}
-	if err := email.updateActivation(sess, true); err != nil {
+	if err := updateActivation(sess, email, true); err != nil {
 		return err
 	}
 	return sess.Commit()
 }
 
-func (email *EmailAddress) updateActivation(e db.Engine, activate bool) error {
+func updateActivation(e db.Engine, email *user_model.EmailAddress, activate bool) error {
 	user, err := getUserByID(e, email.UID)
 	if err != nil {
 		return err
@@ -194,58 +42,17 @@ func (email *EmailAddress) updateActivation(e db.Engine, activate bool) error {
 	return updateUserCols(e, user, "rands")
 }
 
-// DeleteEmailAddress deletes an email address of given user.
-func DeleteEmailAddress(email *EmailAddress) (err error) {
-	if email.IsPrimary {
-		return ErrPrimaryEmailCannotDelete{Email: email.Email}
-	}
-
-	var deleted int64
-	// ask to check UID
-	address := EmailAddress{
-		UID: email.UID,
-	}
-	if email.ID > 0 {
-		deleted, err = db.GetEngine(db.DefaultContext).ID(email.ID).Delete(&address)
-	} else {
-		if email.Email != "" && email.LowerEmail == "" {
-			email.LowerEmail = strings.ToLower(email.Email)
-		}
-		deleted, err = db.GetEngine(db.DefaultContext).
-			Where("lower_email=?", email.LowerEmail).
-			Delete(&address)
-	}
-
-	if err != nil {
-		return err
-	} else if deleted != 1 {
-		return ErrEmailAddressNotExist{Email: email.Email}
-	}
-	return nil
-}
-
-// DeleteEmailAddresses deletes multiple email addresses
-func DeleteEmailAddresses(emails []*EmailAddress) (err error) {
-	for i := range emails {
-		if err = DeleteEmailAddress(emails[i]); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
 // MakeEmailPrimary sets primary email address of given user.
-func MakeEmailPrimary(email *EmailAddress) error {
+func MakeEmailPrimary(email *user_model.EmailAddress) error {
 	has, err := db.GetEngine(db.DefaultContext).Get(email)
 	if err != nil {
 		return err
 	} else if !has {
-		return ErrEmailAddressNotExist{Email: email.Email}
+		return user_model.ErrEmailAddressNotExist{Email: email.Email}
 	}
 
 	if !email.IsActivated {
-		return ErrEmailNotActivated
+		return user_model.ErrEmailNotActivated
 	}
 
 	user := &User{}
@@ -269,7 +76,7 @@ func MakeEmailPrimary(email *EmailAddress) error {
 	}
 
 	// 2. Update old primary email
-	if _, err = sess.Where("uid=? AND is_primary=?", email.UID, true).Cols("is_primary").Update(&EmailAddress{
+	if _, err = sess.Where("uid=? AND is_primary=?", email.UID, true).Cols("is_primary").Update(&user_model.EmailAddress{
 		IsPrimary: false,
 	}); err != nil {
 		return err
@@ -347,7 +154,7 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error)
 	}
 
 	count, err := db.GetEngine(db.DefaultContext).Join("INNER", "`user`", "`user`.ID = email_address.uid").
-		Where(cond).Count(new(EmailAddress))
+		Where(cond).Count(new(user_model.EmailAddress))
 	if err != nil {
 		return nil, 0, fmt.Errorf("Count: %v", err)
 	}
@@ -374,15 +181,16 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error)
 // ActivateUserEmail will change the activated state of an email address,
 // either primary or secondary (all in the email_address table)
 func ActivateUserEmail(userID int64, email string, activate bool) (err error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	// Activate/deactivate a user's secondary email address
 	// First check if there's another user active with the same address
-	addr := EmailAddress{UID: userID, LowerEmail: strings.ToLower(email)}
+	addr := user_model.EmailAddress{UID: userID, LowerEmail: strings.ToLower(email)}
 	if has, err := sess.Get(&addr); err != nil {
 		return err
 	} else if !has {
@@ -393,13 +201,13 @@ func ActivateUserEmail(userID int64, email string, activate bool) (err error) {
 		return nil
 	}
 	if activate {
-		if used, err := isEmailActive(sess, email, addr.ID); err != nil {
+		if used, err := user_model.IsEmailActive(ctx, email, addr.ID); err != nil {
 			return fmt.Errorf("unable to check isEmailActive() for %s: %v", email, err)
 		} else if used {
-			return ErrEmailAlreadyUsed{Email: email}
+			return user_model.ErrEmailAlreadyUsed{Email: email}
 		}
 	}
-	if err = addr.updateActivation(sess, activate); err != nil {
+	if err = updateActivation(sess, &addr, activate); err != nil {
 		return fmt.Errorf("unable to updateActivation() for %d:%s: %w", addr.ID, addr.Email, err)
 	}
 
@@ -423,5 +231,5 @@ func ActivateUserEmail(userID int64, email string, activate bool) (err error) {
 		}
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
diff --git a/models/user_mail_test.go b/models/user_email_test.go
similarity index 50%
rename from models/user_mail_test.go
rename to models/user_email_test.go
index 384f28b7bf..8c2bb48b94 100644
--- a/models/user_mail_test.go
+++ b/models/user_email_test.go
@@ -1,4 +1,4 @@
-// Copyright 2017 The Gitea Authors. All rights reserved.
+// Copyright 2021 The Gitea Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
@@ -8,154 +8,37 @@ import (
 	"testing"
 
 	"code.gitea.io/gitea/models/db"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/util"
 
 	"github.com/stretchr/testify/assert"
 )
 
-func TestGetEmailAddresses(t *testing.T) {
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	emails, _ := GetEmailAddresses(int64(1))
-	if assert.Len(t, emails, 3) {
-		assert.True(t, emails[0].IsPrimary)
-		assert.True(t, emails[2].IsActivated)
-		assert.False(t, emails[2].IsPrimary)
-	}
-
-	emails, _ = GetEmailAddresses(int64(2))
-	if assert.Len(t, emails, 2) {
-		assert.True(t, emails[0].IsPrimary)
-		assert.True(t, emails[0].IsActivated)
-	}
-}
-
-func TestIsEmailUsed(t *testing.T) {
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	isExist, _ := IsEmailUsed("")
-	assert.True(t, isExist)
-	isExist, _ = IsEmailUsed("user11@example.com")
-	assert.True(t, isExist)
-	isExist, _ = IsEmailUsed("user1234567890@example.com")
-	assert.False(t, isExist)
-}
-
-func TestAddEmailAddress(t *testing.T) {
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	assert.NoError(t, AddEmailAddress(&EmailAddress{
-		Email:       "user1234567890@example.com",
-		LowerEmail:  "user1234567890@example.com",
-		IsPrimary:   true,
-		IsActivated: true,
-	}))
-
-	// ErrEmailAlreadyUsed
-	err := AddEmailAddress(&EmailAddress{
-		Email:      "user1234567890@example.com",
-		LowerEmail: "user1234567890@example.com",
-	})
-	assert.Error(t, err)
-	assert.True(t, IsErrEmailAlreadyUsed(err))
-}
-
-func TestAddEmailAddresses(t *testing.T) {
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	// insert multiple email address
-	emails := make([]*EmailAddress, 2)
-	emails[0] = &EmailAddress{
-		Email:       "user1234@example.com",
-		LowerEmail:  "user1234@example.com",
-		IsActivated: true,
-	}
-	emails[1] = &EmailAddress{
-		Email:       "user5678@example.com",
-		LowerEmail:  "user5678@example.com",
-		IsActivated: true,
-	}
-	assert.NoError(t, AddEmailAddresses(emails))
-
-	// ErrEmailAlreadyUsed
-	err := AddEmailAddresses(emails)
-	assert.Error(t, err)
-	assert.True(t, IsErrEmailAlreadyUsed(err))
-}
-
-func TestDeleteEmailAddress(t *testing.T) {
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	assert.NoError(t, DeleteEmailAddress(&EmailAddress{
-		UID:        int64(1),
-		ID:         int64(33),
-		Email:      "user1-2@example.com",
-		LowerEmail: "user1-2@example.com",
-	}))
-
-	assert.NoError(t, DeleteEmailAddress(&EmailAddress{
-		UID:        int64(1),
-		Email:      "user1-3@example.com",
-		LowerEmail: "user1-3@example.com",
-	}))
-
-	// Email address does not exist
-	err := DeleteEmailAddress(&EmailAddress{
-		UID:        int64(1),
-		Email:      "user1234567890@example.com",
-		LowerEmail: "user1234567890@example.com",
-	})
-	assert.Error(t, err)
-}
-
-func TestDeleteEmailAddresses(t *testing.T) {
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	// delete multiple email address
-	emails := make([]*EmailAddress, 2)
-	emails[0] = &EmailAddress{
-		UID:        int64(2),
-		ID:         int64(3),
-		Email:      "user2@example.com",
-		LowerEmail: "user2@example.com",
-	}
-	emails[1] = &EmailAddress{
-		UID:        int64(2),
-		Email:      "user2-2@example.com",
-		LowerEmail: "user2-2@example.com",
-	}
-	assert.NoError(t, DeleteEmailAddresses(emails))
-
-	// ErrEmailAlreadyUsed
-	err := DeleteEmailAddresses(emails)
-	assert.Error(t, err)
-}
-
 func TestMakeEmailPrimary(t *testing.T) {
 	assert.NoError(t, db.PrepareTestDatabase())
 
-	email := &EmailAddress{
+	email := &user_model.EmailAddress{
 		Email: "user567890@example.com",
 	}
 	err := MakeEmailPrimary(email)
 	assert.Error(t, err)
-	assert.EqualError(t, err, ErrEmailAddressNotExist{email.Email}.Error())
+	assert.EqualError(t, err, user_model.ErrEmailAddressNotExist{Email: email.Email}.Error())
 
-	email = &EmailAddress{
+	email = &user_model.EmailAddress{
 		Email: "user11@example.com",
 	}
 	err = MakeEmailPrimary(email)
 	assert.Error(t, err)
-	assert.EqualError(t, err, ErrEmailNotActivated.Error())
+	assert.EqualError(t, err, user_model.ErrEmailNotActivated.Error())
 
-	email = &EmailAddress{
+	email = &user_model.EmailAddress{
 		Email: "user9999999@example.com",
 	}
 	err = MakeEmailPrimary(email)
 	assert.Error(t, err)
 	assert.True(t, IsErrUserNotExist(err))
 
-	email = &EmailAddress{
+	email = &user_model.EmailAddress{
 		Email: "user101@example.com",
 	}
 	err = MakeEmailPrimary(email)
@@ -168,14 +51,14 @@ func TestMakeEmailPrimary(t *testing.T) {
 func TestActivate(t *testing.T) {
 	assert.NoError(t, db.PrepareTestDatabase())
 
-	email := &EmailAddress{
+	email := &user_model.EmailAddress{
 		ID:    int64(1),
 		UID:   int64(1),
 		Email: "user11@example.com",
 	}
-	assert.NoError(t, email.Activate())
+	assert.NoError(t, ActivateEmail(email))
 
-	emails, _ := GetEmailAddresses(int64(1))
+	emails, _ := user_model.GetEmailAddresses(int64(1))
 	assert.Len(t, emails, 3)
 	assert.True(t, emails[0].IsActivated)
 	assert.True(t, emails[0].IsPrimary)
diff --git a/models/user_redirect.go b/models/user_redirect.go
deleted file mode 100644
index fdc730775f..0000000000
--- a/models/user_redirect.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
-	"strings"
-
-	"code.gitea.io/gitea/models/db"
-)
-
-// UserRedirect represents that a user name should be redirected to another
-type UserRedirect struct {
-	ID             int64  `xorm:"pk autoincr"`
-	LowerName      string `xorm:"UNIQUE(s) INDEX NOT NULL"`
-	RedirectUserID int64  // userID to redirect to
-}
-
-func init() {
-	db.RegisterModel(new(UserRedirect))
-}
-
-// LookupUserRedirect look up userID if a user has a redirect name
-func LookupUserRedirect(userName string) (int64, error) {
-	userName = strings.ToLower(userName)
-	redirect := &UserRedirect{LowerName: userName}
-	if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil {
-		return 0, err
-	} else if !has {
-		return 0, ErrUserRedirectNotExist{Name: userName}
-	}
-	return redirect.RedirectUserID, nil
-}
-
-// newUserRedirect create a new user redirect
-func newUserRedirect(e db.Engine, ID int64, oldUserName, newUserName string) error {
-	oldUserName = strings.ToLower(oldUserName)
-	newUserName = strings.ToLower(newUserName)
-
-	if err := deleteUserRedirect(e, newUserName); err != nil {
-		return err
-	}
-
-	if _, err := e.Insert(&UserRedirect{
-		LowerName:      oldUserName,
-		RedirectUserID: ID,
-	}); err != nil {
-		return err
-	}
-	return nil
-}
-
-// deleteUserRedirect delete any redirect from the specified user name to
-// anything else
-func deleteUserRedirect(e db.Engine, userName string) error {
-	userName = strings.ToLower(userName)
-	_, err := e.Delete(&UserRedirect{LowerName: userName})
-	return err
-}
diff --git a/models/user_redirect_test.go b/models/user_redirect_test.go
deleted file mode 100644
index 346bf98b84..0000000000
--- a/models/user_redirect_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
-	"testing"
-
-	"code.gitea.io/gitea/models/db"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestLookupUserRedirect(t *testing.T) {
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	userID, err := LookupUserRedirect("olduser1")
-	assert.NoError(t, err)
-	assert.EqualValues(t, 1, userID)
-
-	_, err = LookupUserRedirect("doesnotexist")
-	assert.True(t, IsErrUserRedirectNotExist(err))
-}
-
-func TestNewUserRedirect(t *testing.T) {
-	// redirect to a completely new name
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
-	assert.NoError(t, newUserRedirect(db.GetEngine(db.DefaultContext), user.ID, user.Name, "newusername"))
-
-	db.AssertExistsAndLoadBean(t, &UserRedirect{
-		LowerName:      user.LowerName,
-		RedirectUserID: user.ID,
-	})
-	db.AssertExistsAndLoadBean(t, &UserRedirect{
-		LowerName:      "olduser1",
-		RedirectUserID: user.ID,
-	})
-}
-
-func TestNewUserRedirect2(t *testing.T) {
-	// redirect to previously used name
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
-	assert.NoError(t, newUserRedirect(db.GetEngine(db.DefaultContext), user.ID, user.Name, "olduser1"))
-
-	db.AssertExistsAndLoadBean(t, &UserRedirect{
-		LowerName:      user.LowerName,
-		RedirectUserID: user.ID,
-	})
-	db.AssertNotExistsBean(t, &UserRedirect{
-		LowerName:      "olduser1",
-		RedirectUserID: user.ID,
-	})
-}
-
-func TestNewUserRedirect3(t *testing.T) {
-	// redirect for a previously-unredirected user
-	assert.NoError(t, db.PrepareTestDatabase())
-
-	user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
-	assert.NoError(t, newUserRedirect(db.GetEngine(db.DefaultContext), user.ID, user.Name, "newusername"))
-
-	db.AssertExistsAndLoadBean(t, &UserRedirect{
-		LowerName:      user.LowerName,
-		RedirectUserID: user.ID,
-	})
-}
diff --git a/models/user_test.go b/models/user_test.go
index 3f3536dafa..58044d1e99 100644
--- a/models/user_test.go
+++ b/models/user_test.go
@@ -12,6 +12,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/login"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/util"
@@ -358,7 +359,7 @@ func TestCreateUserInvalidEmail(t *testing.T) {
 
 	err := CreateUser(user)
 	assert.Error(t, err)
-	assert.True(t, IsErrEmailInvalid(err))
+	assert.True(t, user_model.IsErrEmailInvalid(err))
 }
 
 func TestCreateUser_Issue5882(t *testing.T) {
@@ -511,3 +512,50 @@ func TestUpdateUser(t *testing.T) {
 	user.Email = "no mail@mail.org"
 	assert.Error(t, UpdateUser(user))
 }
+
+func TestNewUserRedirect(t *testing.T) {
+	// redirect to a completely new name
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
+	assert.NoError(t, user_model.NewUserRedirect(db.DefaultContext, user.ID, user.Name, "newusername"))
+
+	db.AssertExistsAndLoadBean(t, &user_model.Redirect{
+		LowerName:      user.LowerName,
+		RedirectUserID: user.ID,
+	})
+	db.AssertExistsAndLoadBean(t, &user_model.Redirect{
+		LowerName:      "olduser1",
+		RedirectUserID: user.ID,
+	})
+}
+
+func TestNewUserRedirect2(t *testing.T) {
+	// redirect to previously used name
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
+	assert.NoError(t, user_model.NewUserRedirect(db.DefaultContext, user.ID, user.Name, "olduser1"))
+
+	db.AssertExistsAndLoadBean(t, &user_model.Redirect{
+		LowerName:      user.LowerName,
+		RedirectUserID: user.ID,
+	})
+	db.AssertNotExistsBean(t, &user_model.Redirect{
+		LowerName:      "olduser1",
+		RedirectUserID: user.ID,
+	})
+}
+
+func TestNewUserRedirect3(t *testing.T) {
+	// redirect for a previously-unredirected user
+	assert.NoError(t, db.PrepareTestDatabase())
+
+	user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
+	assert.NoError(t, user_model.NewUserRedirect(db.DefaultContext, user.ID, user.Name, "newusername"))
+
+	db.AssertExistsAndLoadBean(t, &user_model.Redirect{
+		LowerName:      user.LowerName,
+		RedirectUserID: user.ID,
+	})
+}
diff --git a/modules/context/org.go b/modules/context/org.go
index fd67212a10..d7257361fd 100644
--- a/modules/context/org.go
+++ b/modules/context/org.go
@@ -9,6 +9,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	user_model "code.gitea.io/gitea/models/user"
 )
 
 // Organization contains organization context
@@ -51,10 +52,10 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
 	ctx.Org.Organization, err = models.GetUserByName(orgName)
 	if err != nil {
 		if models.IsErrUserNotExist(err) {
-			redirectUserID, err := models.LookupUserRedirect(orgName)
+			redirectUserID, err := user_model.LookupUserRedirect(orgName)
 			if err == nil {
 				RedirectToUser(ctx, orgName, redirectUserID)
-			} else if models.IsErrUserRedirectNotExist(err) {
+			} else if user_model.IsErrUserRedirectNotExist(err) {
 				ctx.NotFound("GetUserByName", err)
 			} else {
 				ctx.ServerError("LookupUserRedirect", err)
diff --git a/modules/convert/convert.go b/modules/convert/convert.go
index 7d67e2b5b1..80d5ffff05 100644
--- a/modules/convert/convert.go
+++ b/modules/convert/convert.go
@@ -14,6 +14,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/login"
 	"code.gitea.io/gitea/models/unit"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/models/webhook"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
@@ -23,7 +24,7 @@ import (
 )
 
 // ToEmail convert models.EmailAddress to api.Email
-func ToEmail(email *models.EmailAddress) *api.Email {
+func ToEmail(email *user_model.EmailAddress) *api.Email {
 	return &api.Email{
 		Email:    email.Email,
 		Verified: email.IsActivated,
@@ -219,7 +220,7 @@ func ToGPGKey(key *models.GPGKey) *api.GPGKey {
 }
 
 // ToGPGKeyEmail convert models.EmailAddress to api.GPGKeyEmail
-func ToGPGKeyEmail(email *models.EmailAddress) *api.GPGKeyEmail {
+func ToGPGKeyEmail(email *user_model.EmailAddress) *api.GPGKeyEmail {
 	return &api.GPGKeyEmail{
 		Email:    email.Email,
 		Verified: email.IsActivated,
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 2d585b6040..1e4a2851c2 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -12,6 +12,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/login"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
 	"code.gitea.io/gitea/modules/log"
@@ -109,10 +110,10 @@ func CreateUser(ctx *context.APIContext) {
 
 	if err := models.CreateUser(u, overwriteDefault); err != nil {
 		if models.IsErrUserAlreadyExist(err) ||
-			models.IsErrEmailAlreadyUsed(err) ||
+			user_model.IsErrEmailAlreadyUsed(err) ||
 			models.IsErrNameReserved(err) ||
 			models.IsErrNameCharsNotAllowed(err) ||
-			models.IsErrEmailInvalid(err) ||
+			user_model.IsErrEmailInvalid(err) ||
 			models.IsErrNamePatternNotAllowed(err) {
 			ctx.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
@@ -245,7 +246,7 @@ func EditUser(ctx *context.APIContext) {
 	}
 
 	if err := models.UpdateUser(u); err != nil {
-		if models.IsErrEmailAlreadyUsed(err) || models.IsErrEmailInvalid(err) {
+		if user_model.IsErrEmailAlreadyUsed(err) || user_model.IsErrEmailInvalid(err) {
 			ctx.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
 			ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 8f852a96ce..f312df23db 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -71,6 +71,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/unit"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -138,9 +139,9 @@ func repoAssignment() func(ctx *context.APIContext) {
 			owner, err = models.GetUserByName(userName)
 			if err != nil {
 				if models.IsErrUserNotExist(err) {
-					if redirectUserID, err := models.LookupUserRedirect(userName); err == nil {
+					if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil {
 						context.RedirectToUser(ctx.Context, userName, redirectUserID)
-					} else if models.IsErrUserRedirectNotExist(err) {
+					} else if user_model.IsErrUserRedirectNotExist(err) {
 						ctx.NotFound("GetUserByName", err)
 					} else {
 						ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
@@ -421,10 +422,10 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
 			ctx.Org.Organization, err = models.GetOrgByName(ctx.Params(":org"))
 			if err != nil {
 				if models.IsErrOrgNotExist(err) {
-					redirectUserID, err := models.LookupUserRedirect(ctx.Params(":org"))
+					redirectUserID, err := user_model.LookupUserRedirect(ctx.Params(":org"))
 					if err == nil {
 						context.RedirectToUser(ctx.Context, ctx.Params(":org"), redirectUserID)
-					} else if models.IsErrUserRedirectNotExist(err) {
+					} else if user_model.IsErrUserRedirectNotExist(err) {
 						ctx.NotFound("GetOrgByName", err)
 					} else {
 						ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
diff --git a/routers/api/v1/user/email.go b/routers/api/v1/user/email.go
index 9dd35f91b6..6887c306cc 100644
--- a/routers/api/v1/user/email.go
+++ b/routers/api/v1/user/email.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"net/http"
 
-	"code.gitea.io/gitea/models"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
 	"code.gitea.io/gitea/modules/setting"
@@ -28,7 +28,7 @@ func ListEmails(ctx *context.APIContext) {
 	//   "200":
 	//     "$ref": "#/responses/EmailList"
 
-	emails, err := models.GetEmailAddresses(ctx.User.ID)
+	emails, err := user_model.GetEmailAddresses(ctx.User.ID)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetEmailAddresses", err)
 		return
@@ -68,20 +68,20 @@ func AddEmail(ctx *context.APIContext) {
 		return
 	}
 
-	emails := make([]*models.EmailAddress, len(form.Emails))
+	emails := make([]*user_model.EmailAddress, len(form.Emails))
 	for i := range form.Emails {
-		emails[i] = &models.EmailAddress{
+		emails[i] = &user_model.EmailAddress{
 			UID:         ctx.User.ID,
 			Email:       form.Emails[i],
 			IsActivated: !setting.Service.RegisterEmailConfirm,
 		}
 	}
 
-	if err := models.AddEmailAddresses(emails); err != nil {
-		if models.IsErrEmailAlreadyUsed(err) {
-			ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email)
-		} else if models.IsErrEmailInvalid(err) {
-			errMsg := fmt.Sprintf("Email address %s invalid", err.(models.ErrEmailInvalid).Email)
+	if err := user_model.AddEmailAddresses(emails); err != nil {
+		if user_model.IsErrEmailAlreadyUsed(err) {
+			ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
+		} else if user_model.IsErrEmailInvalid(err) {
+			errMsg := fmt.Sprintf("Email address %s invalid", err.(user_model.ErrEmailInvalid).Email)
 			ctx.Error(http.StatusUnprocessableEntity, "", errMsg)
 		} else {
 			ctx.Error(http.StatusInternalServerError, "AddEmailAddresses", err)
@@ -119,16 +119,16 @@ func DeleteEmail(ctx *context.APIContext) {
 		return
 	}
 
-	emails := make([]*models.EmailAddress, len(form.Emails))
+	emails := make([]*user_model.EmailAddress, len(form.Emails))
 	for i := range form.Emails {
-		emails[i] = &models.EmailAddress{
+		emails[i] = &user_model.EmailAddress{
 			Email: form.Emails[i],
 			UID:   ctx.User.ID,
 		}
 	}
 
-	if err := models.DeleteEmailAddresses(emails); err != nil {
-		if models.IsErrEmailAddressNotExist(err) {
+	if err := user_model.DeleteEmailAddresses(emails); err != nil {
+		if user_model.IsErrEmailAddressNotExist(err) {
 			ctx.Error(http.StatusNotFound, "DeleteEmailAddresses", err)
 			return
 		}
diff --git a/routers/api/v1/user/helper.go b/routers/api/v1/user/helper.go
index a3500e0ee6..a7c2e61405 100644
--- a/routers/api/v1/user/helper.go
+++ b/routers/api/v1/user/helper.go
@@ -8,6 +8,7 @@ import (
 	"net/http"
 
 	"code.gitea.io/gitea/models"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
 )
 
@@ -17,7 +18,7 @@ func GetUserByParamsName(ctx *context.APIContext, name string) *models.User {
 	user, err := models.GetUserByName(username)
 	if err != nil {
 		if models.IsErrUserNotExist(err) {
-			if redirectUserID, err2 := models.LookupUserRedirect(username); err2 == nil {
+			if redirectUserID, err2 := user_model.LookupUserRedirect(username); err2 == nil {
 				context.RedirectToUser(ctx.Context, username, redirectUserID)
 			} else {
 				ctx.NotFound("GetUserByName", err)
diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go
index 5cbe70020b..4872ecb920 100644
--- a/routers/web/admin/emails.go
+++ b/routers/web/admin/emails.go
@@ -11,6 +11,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
@@ -128,7 +129,7 @@ func ActivateEmail(ctx *context.Context) {
 
 	if err := models.ActivateUserEmail(uid, email, activate); err != nil {
 		log.Error("ActivateUserEmail(%v,%v,%v): %v", uid, email, activate, err)
-		if models.IsErrEmailAlreadyUsed(err) {
+		if user_model.IsErrEmailAlreadyUsed(err) {
 			ctx.Flash.Error(ctx.Tr("admin.emails.duplicate_active"))
 		} else {
 			ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err))
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index 0041f3d07c..db7fe7b36f 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -14,6 +14,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/login"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
@@ -159,10 +160,10 @@ func NewUserPost(ctx *context.Context) {
 		case models.IsErrUserAlreadyExist(err):
 			ctx.Data["Err_UserName"] = true
 			ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplUserNew, &form)
-		case models.IsErrEmailAlreadyUsed(err):
+		case user_model.IsErrEmailAlreadyUsed(err):
 			ctx.Data["Err_Email"] = true
 			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form)
-		case models.IsErrEmailInvalid(err):
+		case user_model.IsErrEmailInvalid(err):
 			ctx.Data["Err_Email"] = true
 			ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
 		case models.IsErrNameReserved(err):
@@ -351,10 +352,10 @@ func EditUserPost(ctx *context.Context) {
 	}
 
 	if err := models.UpdateUser(u); err != nil {
-		if models.IsErrEmailAlreadyUsed(err) {
+		if user_model.IsErrEmailAlreadyUsed(err) {
 			ctx.Data["Err_Email"] = true
 			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form)
-		} else if models.IsErrEmailInvalid(err) {
+		} else if user_model.IsErrEmailInvalid(err) {
 			ctx.Data["Err_Email"] = true
 			ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
 		} else {
diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go
index 7c5dcd42be..0626d08a7c 100644
--- a/routers/web/repo/http.go
+++ b/routers/web/repo/http.go
@@ -23,6 +23,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/login"
 	"code.gitea.io/gitea/models/unit"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
@@ -112,7 +113,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
 	owner, err := models.GetUserByName(username)
 	if err != nil {
 		if models.IsErrUserNotExist(err) {
-			if redirectUserID, err := models.LookupUserRedirect(username); err == nil {
+			if redirectUserID, err := user_model.LookupUserRedirect(username); err == nil {
 				context.RedirectToUser(ctx, username, redirectUserID)
 			} else {
 				ctx.NotFound(fmt.Sprintf("User %s does not exist", username), nil)
diff --git a/routers/web/user/auth.go b/routers/web/user/auth.go
index 55f304a7cb..ba37c8e3ed 100644
--- a/routers/web/user/auth.go
+++ b/routers/web/user/auth.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/login"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/eventsource"
@@ -181,7 +182,7 @@ func SignInPost(ctx *context.Context) {
 		if models.IsErrUserNotExist(err) {
 			ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
 			log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
-		} else if models.IsErrEmailAlreadyUsed(err) {
+		} else if user_model.IsErrEmailAlreadyUsed(err) {
 			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignIn, &form)
 			log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
 		} else if models.IsErrUserProhibitLogin(err) {
@@ -1273,7 +1274,7 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form int
 // Optionally a template can be specified.
 func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User, allowLink bool) (ok bool) {
 	if err := models.CreateUser(u); err != nil {
-		if allowLink && (models.IsErrUserAlreadyExist(err) || models.IsErrEmailAlreadyUsed(err)) {
+		if allowLink && (models.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
 			if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto {
 				var user *models.User
 				user = &models.User{Name: u.Name}
@@ -1307,10 +1308,10 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{
 		case models.IsErrUserAlreadyExist(err):
 			ctx.Data["Err_UserName"] = true
 			ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, form)
-		case models.IsErrEmailAlreadyUsed(err):
+		case user_model.IsErrEmailAlreadyUsed(err):
 			ctx.Data["Err_Email"] = true
 			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form)
-		case models.IsErrEmailInvalid(err):
+		case user_model.IsErrEmailInvalid(err):
 			ctx.Data["Err_Email"] = true
 			ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
 		case models.IsErrNameReserved(err):
@@ -1499,7 +1500,7 @@ func ActivateEmail(ctx *context.Context) {
 
 	// Verify code.
 	if email := models.VerifyActiveEmailCode(code, emailStr); email != nil {
-		if err := email.Activate(); err != nil {
+		if err := models.ActivateEmail(email); err != nil {
 			ctx.ServerError("ActivateEmail", err)
 		}
 
diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go
index d2a8d83faa..f8fcbf6565 100644
--- a/routers/web/user/profile.go
+++ b/routers/web/user/profile.go
@@ -13,6 +13,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/markdown"
@@ -27,7 +28,7 @@ func GetUserByName(ctx *context.Context, name string) *models.User {
 	user, err := models.GetUserByName(name)
 	if err != nil {
 		if models.IsErrUserNotExist(err) {
-			if redirectUserID, err := models.LookupUserRedirect(name); err == nil {
+			if redirectUserID, err := user_model.LookupUserRedirect(name); err == nil {
 				context.RedirectToUser(ctx, name, redirectUserID)
 			} else {
 				ctx.NotFound("GetUserByName", err)
diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go
index 47014dc814..5ef1c3bdc8 100644
--- a/routers/web/user/setting/account.go
+++ b/routers/web/user/setting/account.go
@@ -11,6 +11,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
@@ -91,7 +92,7 @@ func EmailPost(ctx *context.Context) {
 
 	// Make emailaddress primary.
 	if ctx.FormString("_method") == "PRIMARY" {
-		if err := models.MakeEmailPrimary(&models.EmailAddress{ID: ctx.FormInt64("id")}); err != nil {
+		if err := models.MakeEmailPrimary(&user_model.EmailAddress{ID: ctx.FormInt64("id")}); err != nil {
 			ctx.ServerError("MakeEmailPrimary", err)
 			return
 		}
@@ -110,7 +111,7 @@ func EmailPost(ctx *context.Context) {
 		}
 
 		id := ctx.FormInt64("id")
-		email, err := models.GetEmailAddressByID(ctx.User.ID, id)
+		email, err := user_model.GetEmailAddressByID(ctx.User.ID, id)
 		if err != nil {
 			log.Error("GetEmailAddressByID(%d,%d) error: %v", ctx.User.ID, id, err)
 			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
@@ -174,18 +175,18 @@ func EmailPost(ctx *context.Context) {
 		return
 	}
 
-	email := &models.EmailAddress{
+	email := &user_model.EmailAddress{
 		UID:         ctx.User.ID,
 		Email:       form.Email,
 		IsActivated: !setting.Service.RegisterEmailConfirm,
 	}
-	if err := models.AddEmailAddress(email); err != nil {
-		if models.IsErrEmailAlreadyUsed(err) {
+	if err := user_model.AddEmailAddress(email); err != nil {
+		if user_model.IsErrEmailAlreadyUsed(err) {
 			loadAccountData(ctx)
 
 			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form)
 			return
-		} else if models.IsErrEmailInvalid(err) {
+		} else if user_model.IsErrEmailInvalid(err) {
 			loadAccountData(ctx)
 
 			ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsAccount, &form)
@@ -212,7 +213,7 @@ func EmailPost(ctx *context.Context) {
 
 // DeleteEmail response for delete user's email
 func DeleteEmail(ctx *context.Context) {
-	if err := models.DeleteEmailAddress(&models.EmailAddress{ID: ctx.FormInt64("id"), UID: ctx.User.ID}); err != nil {
+	if err := user_model.DeleteEmailAddress(&user_model.EmailAddress{ID: ctx.FormInt64("id"), UID: ctx.User.ID}); err != nil {
 		ctx.ServerError("DeleteEmail", err)
 		return
 	}
@@ -258,13 +259,13 @@ func DeleteAccount(ctx *context.Context) {
 }
 
 func loadAccountData(ctx *context.Context) {
-	emlist, err := models.GetEmailAddresses(ctx.User.ID)
+	emlist, err := user_model.GetEmailAddresses(ctx.User.ID)
 	if err != nil {
 		ctx.ServerError("GetEmailAddresses", err)
 		return
 	}
 	type UserEmail struct {
-		models.EmailAddress
+		user_model.EmailAddress
 		CanBePrimary bool
 	}
 	pendingActivation := ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName)
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index d181ae1720..4a09006150 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -16,6 +16,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
@@ -60,7 +61,7 @@ func HandleUsernameChange(ctx *context.Context, user *models.User, newName strin
 			switch {
 			case models.IsErrUserAlreadyExist(err):
 				ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
-			case models.IsErrEmailAlreadyUsed(err):
+			case user_model.IsErrEmailAlreadyUsed(err):
 				ctx.Flash.Error(ctx.Tr("form.email_been_used"))
 			case models.IsErrNameReserved(err):
 				ctx.Flash.Error(ctx.Tr("user.form.name_reserved", newName))
@@ -120,7 +121,7 @@ func ProfilePost(ctx *context.Context) {
 	ctx.User.KeepActivityPrivate = form.KeepActivityPrivate
 	ctx.User.Visibility = form.Visibility
 	if err := models.UpdateUserSetting(ctx.User); err != nil {
-		if _, ok := err.(models.ErrEmailAlreadyUsed); ok {
+		if _, ok := err.(user_model.ErrEmailAlreadyUsed); ok {
 			ctx.Flash.Error(ctx.Tr("form.email_been_used"))
 			ctx.Redirect(setting.AppSubURL + "/user/settings")
 			return
diff --git a/services/auth/signin.go b/services/auth/signin.go
index a7ad029456..5477e8643e 100644
--- a/services/auth/signin.go
+++ b/services/auth/signin.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/login"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
 
 	// Register the sources
@@ -32,7 +33,7 @@ func UserSignIn(username, password string) (*models.User, *login.Source, error)
 			return nil, nil, err
 		}
 		if cnt > 1 {
-			return nil, nil, models.ErrEmailAlreadyUsed{
+			return nil, nil, user_model.ErrEmailAlreadyUsed{
 				Email: user.Email,
 			}
 		}
diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go
index cb5ffc2861..6998241ad7 100644
--- a/services/auth/source/pam/source_authenticate.go
+++ b/services/auth/source/pam/source_authenticate.go
@@ -10,6 +10,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/login"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/auth/pam"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/services/mailer"
@@ -39,13 +40,13 @@ func (source *Source) Authenticate(user *models.User, userName, password string)
 	if idx > -1 {
 		username = pamLogin[:idx]
 	}
-	if models.ValidateEmail(email) != nil {
+	if user_model.ValidateEmail(email) != nil {
 		if source.EmailDomain != "" {
 			email = fmt.Sprintf("%s@%s", username, source.EmailDomain)
 		} else {
 			email = fmt.Sprintf("%s@%s", username, setting.Service.NoReplyAddress)
 		}
-		if models.ValidateEmail(email) != nil {
+		if user_model.ValidateEmail(email) != nil {
 			email = uuid.New().String() + "@localhost"
 		}
 	}
diff --git a/services/mailer/mail.go b/services/mailer/mail.go
index bd8f059c5e..b579bf383a 100644
--- a/services/mailer/mail.go
+++ b/services/mailer/mail.go
@@ -16,6 +16,7 @@ import (
 	texttmpl "text/template"
 
 	"code.gitea.io/gitea/models"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/emoji"
 	"code.gitea.io/gitea/modules/log"
@@ -112,7 +113,7 @@ func SendResetPasswordMail(u *models.User) {
 }
 
 // SendActivateEmailMail sends confirmation email to confirm new email address
-func SendActivateEmailMail(u *models.User, email *models.EmailAddress) {
+func SendActivateEmailMail(u *models.User, email *user_model.EmailAddress) {
 	if setting.MailService == nil {
 		// No mail service configured
 		return