diff --git a/integration/user_api_test.go b/integration/user_api_test.go
index 9293e3eca7eeafa6350b6800dcf47b191e658192..8d553602a261aa6bdbd39e7b875a2a1bd45cfcc9 100644
--- a/integration/user_api_test.go
+++ b/integration/user_api_test.go
@@ -45,8 +45,9 @@ var (
 		},
 		{
 			User: user.User{
-				ID:    "ID-2",
-				Email: "Email-2@example.com",
+				ID:            "ID-2",
+				Email:         "Email-2@example.com",
+				EmailVerified: true,
 			},
 		},
 		{
@@ -582,6 +583,184 @@ func TestDisableUser(t *testing.T) {
 	}
 }
 
+func TestResendEmailInvitation(t *testing.T) {
+	tests := []struct {
+		req       schema.ResendEmailInvitationRequest
+		cantEmail bool
+		userID    string
+		email     string
+		token     string
+
+		wantResponse schema.ResendEmailInvitationResponse
+		wantCode     int
+	}{
+		{
+
+			req: schema.ResendEmailInvitationRequest{
+				RedirectURL: testRedirectURL.String(),
+			},
+
+			userID: "ID-3",
+			email:  "Email-3@example.com",
+			token:  userGoodToken,
+
+			wantResponse: schema.ResendEmailInvitationResponse{
+				EmailSent: true,
+			},
+		},
+		{
+
+			req: schema.ResendEmailInvitationRequest{
+				RedirectURL: testRedirectURL.String(),
+			},
+
+			userID:    "ID-3",
+			email:     "Email-3@example.com",
+			cantEmail: true,
+			token:     userGoodToken,
+
+			wantResponse: schema.ResendEmailInvitationResponse{
+				ResetPasswordLink: testResetPasswordURL.String(),
+			},
+		},
+		{
+
+			req: schema.ResendEmailInvitationRequest{
+				RedirectURL: "http://scammers.com",
+			},
+
+			userID: "ID-3",
+			email:  "Email-3@example.com",
+			token:  userGoodToken,
+
+			wantCode: http.StatusBadRequest,
+		},
+		{
+
+			req: schema.ResendEmailInvitationRequest{
+				RedirectURL: testRedirectURL.String(),
+			},
+
+			userID: "ID-2",
+			email:  "Email-2@example.com",
+			token:  userGoodToken,
+
+			wantCode: http.StatusBadRequest,
+		},
+		{
+			req: schema.ResendEmailInvitationRequest{
+				RedirectURL: testRedirectURL.String(),
+			},
+
+			userID: "ID-3",
+			email:  "Email-3@example.com",
+			token:  userBadTokenClientNotAdmin,
+
+			wantCode: http.StatusForbidden,
+		},
+		{
+			req: schema.ResendEmailInvitationRequest{
+				RedirectURL: testRedirectURL.String(),
+			},
+
+			userID: "ID-3",
+			email:  "Email-3@example.com",
+			token:  userBadClientID,
+
+			wantCode: http.StatusUnauthorized,
+		},
+		{
+			req: schema.ResendEmailInvitationRequest{
+				RedirectURL: testRedirectURL.String(),
+			},
+
+			userID: "ID-3",
+			email:  "Email-3@example.com",
+			token:  userBadTokenExpired,
+
+			wantCode: http.StatusUnauthorized,
+		},
+		{
+			req: schema.ResendEmailInvitationRequest{
+				RedirectURL: testRedirectURL.String(),
+			},
+
+			userID: "ID-3",
+			email:  "Email-3@example.com",
+			token:  userBadTokenDisabled,
+
+			wantCode: http.StatusUnauthorized,
+		},
+		{
+			req: schema.ResendEmailInvitationRequest{
+				RedirectURL: testRedirectURL.String(),
+			},
+
+			userID: "ID-3",
+			email:  "Email-3@example.com",
+			token:  userBadTokenNotAdmin,
+
+			wantCode: http.StatusUnauthorized,
+		},
+	}
+	for i, tt := range tests {
+		func() {
+			f := makeUserAPITestFixtures()
+			defer f.close()
+			f.trans.Token = tt.token
+			f.emailer.cantEmail = tt.cantEmail
+
+			page, err := f.client.Users.ResendEmailInvitation(tt.userID, &tt.req).Do()
+			if tt.wantCode != 0 {
+				if err == nil {
+					t.Errorf("case %d: err was nil", i)
+					return
+				}
+				gErr, ok := err.(*googleapi.Error)
+				if !ok {
+					t.Errorf("case %d: not a googleapi Error: %q", i, err)
+					return
+				}
+
+				if gErr.Code != tt.wantCode {
+					t.Errorf("case %d: want=%d, got=%d", i, tt.wantCode, gErr.Code)
+					return
+				}
+				return
+			}
+
+			if err != nil {
+				t.Errorf("case %d: want nil err, got: %v %T ", i, err, err)
+				return
+			}
+
+			if diff := pretty.Compare(tt.wantResponse, page); diff != "" {
+				t.Errorf("case %d: Compare(want, got) = %v", i, diff)
+				return
+			}
+
+			urlParsed, err := url.Parse(tt.req.RedirectURL)
+			if err != nil {
+				t.Errorf("case %d unexpected err: %v", i, err)
+				return
+			}
+
+			wantEmalier := testEmailer{
+				cantEmail:       tt.cantEmail,
+				lastEmail:       tt.email,
+				lastClientID:    "XXX",
+				lastWasInvite:   true,
+				lastRedirectURL: *urlParsed,
+			}
+			if diff := pretty.Compare(wantEmalier, f.emailer); diff != "" {
+				t.Errorf("case %d: Compare(want, got) = %v", i, diff)
+				return
+			}
+
+		}()
+	}
+}
+
 type testEmailer struct {
 	cantEmail       bool
 	lastEmail       string
diff --git a/schema/workerschema/README.md b/schema/workerschema/README.md
index 8ff8c31b794949beb1309d2fcaaa3dc7a73ac626..f8f2bf8c72f3ae422dcd531d24590900e5710a14 100644
--- a/schema/workerschema/README.md
+++ b/schema/workerschema/README.md
@@ -59,6 +59,27 @@ __Version:__ v1
 }
 ```
 
+### ResendEmailInvitationRequest
+
+
+
+```
+{
+    redirectURL: string
+}
+```
+
+### ResendEmailInvitationResponse
+
+
+
+```
+{
+    emailSent: boolean,
+    resetPasswordLink: string
+}
+```
+
 ### User
 
 
@@ -303,3 +324,30 @@ __Version:__ v1
 | default | Unexpected error |  |
 
 
+### POST /users/{id}/resend-invitation
+
+> __Summary__
+
+> ResendEmailInvitation Users
+
+> __Description__
+
+> Resend invitation email to an existing user with unverified email.
+
+
+> __Parameters__
+
+> |Name|Located in|Description|Required|Type|
+|:-----|:-----|:-----|:-----|:-----|
+| id | path |  | Yes | string | 
+|  | body |  | Yes | [ResendEmailInvitationRequest](#resendemailinvitationrequest) | 
+
+
+> __Responses__
+
+> |Code|Description|Type|
+|:-----|:-----|:-----|
+| 200 |  | [ResendEmailInvitationResponse](#resendemailinvitationresponse) |
+| default | Unexpected error |  |
+
+
diff --git a/schema/workerschema/v1-gen.go b/schema/workerschema/v1-gen.go
index 7b9aa784a94065d77597845ad1a3fe7d0fb0d107..2d410541e70df612746ab953eea3c2939c192431 100644
--- a/schema/workerschema/v1-gen.go
+++ b/schema/workerschema/v1-gen.go
@@ -14,13 +14,12 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"google.golang.org/api/googleapi"
 	"io"
 	"net/http"
 	"net/url"
 	"strconv"
 	"strings"
-
-	"google.golang.org/api/googleapi"
 )
 
 // Always reference these packages, just in case the auto-generated code
@@ -103,6 +102,16 @@ type Error struct {
 	Error_description string `json:"error_description,omitempty"`
 }
 
+type ResendEmailInvitationRequest struct {
+	RedirectURL string `json:"redirectURL,omitempty"`
+}
+
+type ResendEmailInvitationResponse struct {
+	EmailSent bool `json:"emailSent,omitempty"`
+
+	ResetPasswordLink string `json:"resetPasswordLink,omitempty"`
+}
+
 type User struct {
 	Admin bool `json:"admin,omitempty"`
 
@@ -607,3 +616,87 @@ func (c *UsersListCall) Do() (*UsersResponse, error) {
 	// }
 
 }
+
+// method id "dex.User.ResendEmailInvitation":
+
+type UsersResendEmailInvitationCall struct {
+	s                            *Service
+	id                           string
+	resendemailinvitationrequest *ResendEmailInvitationRequest
+	opt_                         map[string]interface{}
+}
+
+// ResendEmailInvitation: Resend invitation email to an existing user
+// with unverified email.
+func (r *UsersService) ResendEmailInvitation(id string, resendemailinvitationrequest *ResendEmailInvitationRequest) *UsersResendEmailInvitationCall {
+	c := &UsersResendEmailInvitationCall{s: r.s, opt_: make(map[string]interface{})}
+	c.id = id
+	c.resendemailinvitationrequest = resendemailinvitationrequest
+	return c
+}
+
+// Fields allows partial responses to be retrieved.
+// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
+// for more information.
+func (c *UsersResendEmailInvitationCall) Fields(s ...googleapi.Field) *UsersResendEmailInvitationCall {
+	c.opt_["fields"] = googleapi.CombineFields(s)
+	return c
+}
+
+func (c *UsersResendEmailInvitationCall) Do() (*ResendEmailInvitationResponse, error) {
+	var body io.Reader = nil
+	body, err := googleapi.WithoutDataWrapper.JSONReader(c.resendemailinvitationrequest)
+	if err != nil {
+		return nil, err
+	}
+	ctype := "application/json"
+	params := make(url.Values)
+	params.Set("alt", "json")
+	if v, ok := c.opt_["fields"]; ok {
+		params.Set("fields", fmt.Sprintf("%v", v))
+	}
+	urls := googleapi.ResolveRelative(c.s.BasePath, "users/{id}/resend-invitation")
+	urls += "?" + params.Encode()
+	req, _ := http.NewRequest("POST", urls, body)
+	googleapi.Expand(req.URL, map[string]string{
+		"id": c.id,
+	})
+	req.Header.Set("Content-Type", ctype)
+	req.Header.Set("User-Agent", "google-api-go-client/0.5")
+	res, err := c.s.client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer googleapi.CloseBody(res)
+	if err := googleapi.CheckResponse(res); err != nil {
+		return nil, err
+	}
+	var ret *ResendEmailInvitationResponse
+	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
+		return nil, err
+	}
+	return ret, nil
+	// {
+	//   "description": "Resend invitation email to an existing user with unverified email.",
+	//   "httpMethod": "POST",
+	//   "id": "dex.User.ResendEmailInvitation",
+	//   "parameterOrder": [
+	//     "id"
+	//   ],
+	//   "parameters": {
+	//     "id": {
+	//       "location": "path",
+	//       "required": true,
+	//       "type": "string"
+	//     }
+	//   },
+	//   "path": "users/{id}/resend-invitation",
+	//   "request": {
+	//     "$ref": "ResendEmailInvitationRequest"
+	//   },
+	//   "response": {
+	//     "$ref": "ResendEmailInvitationResponse"
+	//   }
+	// }
+
+}
diff --git a/schema/workerschema/v1-json.go b/schema/workerschema/v1-json.go
index 6c408dc5543e7d1695f1d80c09395acd255a96d4..4c1e1ba8907f04c57a7d66cee7c0b8c2e3cd7d24 100644
--- a/schema/workerschema/v1-json.go
+++ b/schema/workerschema/v1-json.go
@@ -188,6 +188,28 @@ const DiscoveryJSON = `{
           "type": "boolean"
         }
       }
+    },
+    "ResendEmailInvitationRequest": {
+      "id": "UserDisableRequest",
+      "type": "object",
+      "properties": {
+        "redirectURL": {
+          "type": "string",
+          "format": "url"
+        }
+      }
+    },
+    "ResendEmailInvitationResponse": {
+      "id": "UserDisableResponse",
+      "type": "object",
+      "properties": {
+        "resetPasswordLink": {
+          "type": "string"
+        },
+        "emailSent": {
+          "type": "boolean"
+        }
+      }
     }
   },
   "resources": {
@@ -295,6 +317,28 @@ const DiscoveryJSON = `{
           "response": {
             "$ref": "UserDisableResponse"
           }
+        },
+        "ResendEmailInvitation": {
+          "id": "dex.User.ResendEmailInvitation",
+          "description": "Resend invitation email to an existing user with unverified email.",
+          "httpMethod": "POST",
+          "path": "users/{id}/resend-invitation",
+          "parameters": {
+            "id": {
+              "type": "string",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "parameterOrder": [
+            "id"
+          ],
+          "request": {
+            "$ref": "ResendEmailInvitationRequest"
+          },
+          "response": {
+            "$ref": "ResendEmailInvitationResponse"
+          }
         }
       }
     }
diff --git a/schema/workerschema/v1.json b/schema/workerschema/v1.json
index 7d3570f98fb705a6812bb37f4fe30a88c4d9d82f..1674119bed61326ae5cb6026f08c8fc5bbfcc34c 100644
--- a/schema/workerschema/v1.json
+++ b/schema/workerschema/v1.json
@@ -182,6 +182,28 @@
           "type": "boolean"
         }
       }
+    },
+    "ResendEmailInvitationRequest": {
+      "id": "UserDisableRequest",
+      "type": "object",
+      "properties": {
+        "redirectURL": {
+          "type": "string",
+          "format": "url"
+        }
+      }
+    },
+    "ResendEmailInvitationResponse": {
+      "id": "UserDisableResponse",
+      "type": "object",
+      "properties": {
+        "resetPasswordLink": {
+          "type": "string"
+        },
+        "emailSent": {
+          "type": "boolean"
+        }
+      }
     }
   },
   "resources": {
@@ -289,6 +311,28 @@
           "response": {
             "$ref": "UserDisableResponse"
           }
+        },
+        "ResendEmailInvitation": {
+          "id": "dex.User.ResendEmailInvitation",
+          "description": "Resend invitation email to an existing user with unverified email.",
+          "httpMethod": "POST",
+          "path": "users/{id}/resend-invitation",
+          "parameters": {
+            "id": {
+              "type": "string",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "parameterOrder": [
+            "id"
+          ],
+          "request": {
+            "$ref": "ResendEmailInvitationRequest"
+          },
+          "response": {
+            "$ref": "ResendEmailInvitationResponse"
+          }
         }
       }
     }
diff --git a/server/user.go b/server/user.go
index b5cfb95b617438e768e22025c72ee20ea776e1f6..8481ee3f3c086f9bd5b4f2c7f53ad4596550ad4d 100644
--- a/server/user.go
+++ b/server/user.go
@@ -24,11 +24,12 @@ const (
 )
 
 var (
-	UsersSubTree         = "/users"
-	UsersListEndpoint    = addBasePath(UsersSubTree)
-	UsersCreateEndpoint  = addBasePath(UsersSubTree)
-	UsersGetEndpoint     = addBasePath(UsersSubTree + "/:id")
-	UsersDisableEndpoint = addBasePath(UsersSubTree + "/:id/disable")
+	UsersSubTree                  = "/users"
+	UsersListEndpoint             = addBasePath(UsersSubTree)
+	UsersCreateEndpoint           = addBasePath(UsersSubTree)
+	UsersGetEndpoint              = addBasePath(UsersSubTree + "/:id")
+	UsersDisableEndpoint          = addBasePath(UsersSubTree + "/:id/disable")
+	UsersResendInvitationEndpoint = addBasePath(UsersSubTree + "/:id/resend-invitation")
 )
 
 type UserMgmtServer struct {
@@ -55,6 +56,7 @@ func (s *UserMgmtServer) HTTPHandler() http.Handler {
 	r.POST(UsersCreateEndpoint, s.authAPIHandle(s.createUser))
 	r.POST(UsersDisableEndpoint, s.authAPIHandle(s.disableUser))
 	r.GET(UsersGetEndpoint, s.authAPIHandle(s.getUser))
+	r.POST(UsersResendInvitationEndpoint, s.authAPIHandle(s.resendInvitationEmail))
 	return r
 }
 
@@ -161,6 +163,34 @@ func (s *UserMgmtServer) disableUser(w http.ResponseWriter, r *http.Request, ps
 	writeResponseWithBody(w, http.StatusOK, resp)
 }
 
+func (s *UserMgmtServer) resendInvitationEmail(w http.ResponseWriter, r *http.Request, ps httprouter.Params, creds api.Creds) {
+	id := ps.ByName("id")
+	if id == "" {
+		writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "id is required"))
+		return
+	}
+	resendEmailInvitationReq := schema.ResendEmailInvitationRequest{}
+	if err := json.NewDecoder(r.Body).Decode(&resendEmailInvitationReq); err != nil {
+		writeInvalidRequest(w, "cannot parse JSON body")
+		return
+	}
+
+	redirURL, err := url.Parse(resendEmailInvitationReq.RedirectURL)
+	if err != nil {
+		writeAPIError(w, http.StatusBadRequest,
+			newAPIError(errorInvalidRequest, "redirectURL must be a valid URL"))
+		return
+	}
+
+	resendEmailInvitationResponse, err := s.api.ResendEmailInvitation(creds, id, *redirURL)
+	if err != nil {
+		s.writeError(w, err)
+		return
+	}
+
+	writeResponseWithBody(w, http.StatusOK, resendEmailInvitationResponse)
+}
+
 func (s *UserMgmtServer) writeError(w http.ResponseWriter, err error) {
 	log.Errorf("Error calling user management API: %v: ", err)
 	if apiErr, ok := err.(api.Error); ok {
diff --git a/user/api/api.go b/user/api/api.go
index d068cd7f1ac5e89bbd861af862e218b701b57243..9eca4b38edbee94e5fb35f27f5fb2b3a70956be5 100644
--- a/user/api/api.go
+++ b/user/api/api.go
@@ -24,7 +24,8 @@ var (
 		client.ErrorNotFound:     ErrorInvalidClient,
 	}
 
-	ErrorInvalidEmail = newError("invalid_email", "invalid email.", http.StatusBadRequest)
+	ErrorInvalidEmail  = newError("invalid_email", "invalid email.", http.StatusBadRequest)
+	ErrorVerifiedEmail = newError("verified_email", "Email already verified.", http.StatusBadRequest)
 
 	ErrorInvalidClient = newError("invalid_client", "invalid email.", http.StatusBadRequest)
 
@@ -188,6 +189,50 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
 	}, nil
 }
 
+func (u *UsersAPI) ResendEmailInvitation(creds Creds, userID string, redirURL url.URL) (schema.ResendEmailInvitationResponse, error) {
+	log.Infof("userAPI: ResendEmailInvitation")
+	if !u.Authorize(creds) {
+		return schema.ResendEmailInvitationResponse{}, ErrorUnauthorized
+	}
+
+	metadata, err := u.clientIdentityRepo.Metadata(creds.ClientID)
+	if err != nil {
+		return schema.ResendEmailInvitationResponse{}, mapError(err)
+	}
+
+	validRedirURL, err := client.ValidRedirectURL(&redirURL, metadata.RedirectURIs)
+	if err != nil {
+		return schema.ResendEmailInvitationResponse{}, ErrorInvalidRedirectURL
+	}
+
+	// Retrieve user to check if it's already created
+	userUser, err := u.manager.Get(userID)
+	if err != nil {
+		return schema.ResendEmailInvitationResponse{}, mapError(err)
+	}
+
+	// Check if email is verified
+	if userUser.EmailVerified {
+		return schema.ResendEmailInvitationResponse{}, ErrorVerifiedEmail
+	}
+
+	url, err := u.emailer.SendInviteEmail(userUser.Email, validRedirURL, creds.ClientID)
+
+	// An email is sent only if we don't get a link and there's no error.
+	emailSent := err == nil && url == nil
+
+	// If email is not sent a reset link will be generated
+	var resetLink string
+	if url != nil {
+		resetLink = url.String()
+	}
+
+	return schema.ResendEmailInvitationResponse{
+		EmailSent:         emailSent,
+		ResetPasswordLink: resetLink,
+	}, nil
+}
+
 func (u *UsersAPI) ListUsers(creds Creds, maxResults int, nextPageToken string) ([]*schema.User, string, error) {
 	log.Infof("userAPI: ListUsers")
 
diff --git a/user/api/api_test.go b/user/api/api_test.go
index ac44389e773abf71b24462003f25d8b053198ec5..5412cb2deef39fe2253b45e10938afc2078a54e0 100644
--- a/user/api/api_test.go
+++ b/user/api/api_test.go
@@ -99,9 +99,10 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) {
 				},
 			}, {
 				User: user.User{
-					ID:        "ID-2",
-					Email:     "id2@example.com",
-					CreatedAt: clock.Now(),
+					ID:            "ID-2",
+					Email:         "id2@example.com",
+					EmailVerified: true,
+					CreatedAt:     clock.Now(),
 				},
 			}, {
 				User: user.User{
@@ -463,3 +464,101 @@ func TestDisableUsers(t *testing.T) {
 		}
 	}
 }
+func TestResendEmailInvitation(t *testing.T) {
+	tests := []struct {
+		creds     Creds
+		userID    string
+		email     string
+		redirURL  url.URL
+		cantEmail bool
+
+		wantResponse schema.ResendEmailInvitationResponse
+		wantErr      error
+	}{
+		{
+			creds:    goodCreds,
+			userID:   "ID-1",
+			email:    "id1@example.com",
+			redirURL: validRedirURL,
+
+			wantResponse: schema.ResendEmailInvitationResponse{
+				EmailSent: true,
+			},
+		},
+		{
+			creds:     goodCreds,
+			userID:    "ID-1",
+			email:     "id1@example.com",
+			redirURL:  validRedirURL,
+			cantEmail: true,
+
+			wantResponse: schema.ResendEmailInvitationResponse{
+				EmailSent:         false,
+				ResetPasswordLink: resetPasswordURL.String(),
+			},
+		},
+		{
+			creds:    badCreds,
+			userID:   "ID-1",
+			email:    "id1@example.com",
+			redirURL: validRedirURL,
+
+			wantErr: ErrorUnauthorized,
+		},
+		{
+			creds:    goodCreds,
+			userID:   "ID-1",
+			email:    "id1@example.com",
+			redirURL: url.URL{Host: "scammers.com"},
+
+			wantErr: ErrorInvalidRedirectURL,
+		},
+		{
+			creds:    goodCreds,
+			userID:   "ID-2",
+			email:    "id2@example.com",
+			redirURL: validRedirURL,
+
+			wantErr: ErrorVerifiedEmail,
+		},
+		{
+			creds:    goodCreds,
+			userID:   "non-existent",
+			email:    "non-existent@example.com",
+			redirURL: validRedirURL,
+
+			wantErr: ErrorResourceNotFound,
+		},
+	}
+
+	for i, tt := range tests {
+		api, emailer := makeTestFixtures()
+		emailer.cantEmail = tt.cantEmail
+
+		response, err := api.ResendEmailInvitation(tt.creds, tt.userID, tt.redirURL)
+		if tt.wantErr != nil {
+			if err != tt.wantErr {
+				t.Errorf("case %d: want=%q, got=%q", i, tt.wantErr, err)
+			}
+			continue
+		}
+		if err != nil {
+			t.Errorf("case %d: want nil err, got: %q ", i, err)
+		}
+
+		if diff := pretty.Compare(tt.wantResponse, response); diff != "" {
+			t.Errorf("case %d: Compare(want, got) = %v", i, diff)
+		}
+
+		wantEmailer := testEmailer{
+			cantEmail:       tt.cantEmail,
+			lastEmail:       tt.email,
+			lastClientID:    tt.creds.ClientID,
+			lastRedirectURL: tt.redirURL,
+			lastWasInvite:   true,
+		}
+		if diff := pretty.Compare(wantEmailer, emailer); diff != "" {
+			t.Errorf("case %d: Compare(want, got) = %v", i, diff)
+		}
+	}
+}