diff --git a/modules/structs/user_email.go b/modules/structs/user_email.go
index 6a11e040af..9319667e8f 100644
--- a/modules/structs/user_email.go
+++ b/modules/structs/user_email.go
@@ -1,4 +1,5 @@
 // Copyright 2015 The Gogs Authors. All rights reserved.
+// Copyright 2023 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
 package structs
@@ -9,6 +10,8 @@ type Email struct {
 	Email    string `json:"email"`
 	Verified bool   `json:"verified"`
 	Primary  bool   `json:"primary"`
+	UserID   int64  `json:"user_id"`
+	UserName string `json:"username"`
 }
 
 // CreateEmailOption options when creating email addresses
diff --git a/routers/api/v1/admin/email.go b/routers/api/v1/admin/email.go
new file mode 100644
index 0000000000..8d0491e070
--- /dev/null
+++ b/routers/api/v1/admin/email.go
@@ -0,0 +1,87 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package admin
+
+import (
+	"net/http"
+
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/context"
+	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/routers/api/v1/utils"
+	"code.gitea.io/gitea/services/convert"
+)
+
+// GetAllEmails
+func GetAllEmails(ctx *context.APIContext) {
+	// swagger:operation GET /admin/emails admin adminGetAllEmails
+	// ---
+	// summary: List all emails
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: page
+	//   in: query
+	//   description: page number of results to return (1-based)
+	//   type: integer
+	// - name: limit
+	//   in: query
+	//   description: page size of results
+	//   type: integer
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/EmailList"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+
+	listOptions := utils.GetListOptions(ctx)
+
+	emails, maxResults, err := user_model.SearchEmails(&user_model.SearchEmailOptions{
+		Keyword:     ctx.Params(":email"),
+		ListOptions: listOptions,
+	})
+	if err != nil {
+		ctx.Error(http.StatusInternalServerError, "GetAllEmails", err)
+		return
+	}
+
+	results := make([]*api.Email, len(emails))
+	for i := range emails {
+		results[i] = convert.ToEmailSearch(emails[i])
+	}
+
+	ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
+	ctx.SetTotalCountHeader(maxResults)
+	ctx.JSON(http.StatusOK, &results)
+}
+
+// SearchEmail
+func SearchEmail(ctx *context.APIContext) {
+	// swagger:operation GET /admin/emails/search admin adminSearchEmails
+	// ---
+	// summary: Search all emails
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: q
+	//   in: query
+	//   description: keyword
+	//   type: string
+	// - name: page
+	//   in: query
+	//   description: page number of results to return (1-based)
+	//   type: integer
+	// - name: limit
+	//   in: query
+	//   description: page size of results
+	//   type: integer
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/EmailList"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+
+	ctx.SetParams(":email", ctx.FormTrim("q"))
+	GetAllEmails(ctx)
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 7001dc72ac..5c32164fa7 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -1260,6 +1260,10 @@ func Routes(ctx gocontext.Context) *web.Route {
 					m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser)
 				}, context_service.UserAssignmentAPI())
 			})
+			m.Group("/emails", func() {
+				m.Get("", admin.GetAllEmails)
+				m.Get("/search", admin.SearchEmail)
+			})
 			m.Group("/unadopted", func() {
 				m.Get("", admin.ListUnadoptedRepositories)
 				m.Post("/{username}/{reponame}", admin.AdoptRepository)
diff --git a/services/convert/convert.go b/services/convert/convert.go
index 5f2100a039..bce0e7ba21 100644
--- a/services/convert/convert.go
+++ b/services/convert/convert.go
@@ -38,6 +38,17 @@ func ToEmail(email *user_model.EmailAddress) *api.Email {
 	}
 }
 
+// ToEmail convert models.EmailAddress to api.Email
+func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email {
+	return &api.Email{
+		Email:    email.Email,
+		Verified: email.IsActivated,
+		Primary:  email.IsPrimary,
+		UserID:   email.UID,
+		UserName: email.Name,
+	}
+}
+
 // ToBranch convert a git.Commit and git.Branch to an api.Branch
 func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
 	if bp == nil {
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 7dc7f563c2..9c89b21fca 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -138,6 +138,80 @@
         }
       }
     },
+    "/admin/emails": {
+      "get": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "admin"
+        ],
+        "summary": "List all emails",
+        "operationId": "adminGetAllEmails",
+        "parameters": [
+          {
+            "type": "integer",
+            "description": "page number of results to return (1-based)",
+            "name": "page",
+            "in": "query"
+          },
+          {
+            "type": "integer",
+            "description": "page size of results",
+            "name": "limit",
+            "in": "query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/EmailList"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          }
+        }
+      }
+    },
+    "/admin/emails/search": {
+      "get": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "admin"
+        ],
+        "summary": "Search all emails",
+        "operationId": "adminSearchEmails",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "keyword",
+            "name": "q",
+            "in": "query"
+          },
+          {
+            "type": "integer",
+            "description": "page number of results to return (1-based)",
+            "name": "page",
+            "in": "query"
+          },
+          {
+            "type": "integer",
+            "description": "page size of results",
+            "name": "limit",
+            "in": "query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/EmailList"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          }
+        }
+      }
+    },
     "/admin/hooks": {
       "get": {
         "produces": [
@@ -16999,6 +17073,15 @@
           "type": "boolean",
           "x-go-name": "Primary"
         },
+        "user_id": {
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "UserID"
+        },
+        "username": {
+          "type": "string",
+          "x-go-name": "UserName"
+        },
         "verified": {
           "type": "boolean",
           "x-go-name": "Verified"