Index: ow/models/user.py
===================================================================
--- ow/models/user.py	(revision 38171c6682d26c4ff2094474bec5982c07218687)
+++ ow/models/user.py	(revision 8bae554b97b0673cf21e50e15b41dfc5223a45b1)
@@ -42,4 +42,5 @@
         self.verified = False
         self.verification_token = None
+        self.verification_tokens_sent = 0
         super(User, self).__init__()
 
Index: ow/templates/login.pt
===================================================================
--- ow/templates/login.pt	(revision 38171c6682d26c4ff2094474bec5982c07218687)
+++ ow/templates/login.pt	(revision 8bae554b97b0673cf21e50e15b41dfc5223a45b1)
@@ -20,5 +20,12 @@
 
           <div id="messages" class="message message-error"
-               tal:content="message" tal:condition="message != ''"></div>
+               tal:condition="message != ''">
+            <tal:message tal:content="message"></tal:message>
+            <tal:resend-verify tal:condition="resend_verify_link is not None">
+              <br>
+              <a href="" tal:attributes="href resend_verify_link"
+                 i18n:translate="">Please send me the verification link again</a>
+            </tal:resend-verify>
+          </div>
 
           <fieldset>
Index: ow/tests/views/test_user.py
===================================================================
--- ow/tests/views/test_user.py	(revision 38171c6682d26c4ff2094474bec5982c07218687)
+++ ow/tests/views/test_user.py	(revision 8bae554b97b0673cf21e50e15b41dfc5223a45b1)
@@ -172,7 +172,75 @@
         assert john.verified
 
+    @patch('ow.views.user.send_verification_email')
+    def test_resend_verification_already_verified(
+            self, sve, dummy_request, john):
+        john.verified = True
+        assert john.verification_token is None
+        response = user_views.resend_verification_link(john, dummy_request)
+        assert isinstance(response, HTTPFound)
+        assert response.location == dummy_request.resource_url(
+            dummy_request.root, 'login', query={'message': 'already-verified'}
+        )
+        # no emails have been sent
+        assert not sve.called
+        # the token has not been modified
+        assert john.verification_token is None
+
+    @patch('ow.views.user.send_verification_email')
+    def test_resend_verification(self, sve, dummy_request, john):
+        john.verified = False
+        john.verification_token = 'faked-uuid4-verification-token'
+        assert john.verification_tokens_sent == 0
+        response = user_views.resend_verification_link(john, dummy_request)
+        assert isinstance(response, HTTPFound)
+        assert response.location == dummy_request.resource_url(
+            dummy_request.root, 'login',
+            query={'message': 'link-sent', 'email': john.email}
+        )
+        # no emails have been sent
+        sve.assert_called_once_with(dummy_request, john)
+        # the token has not been modified
+        assert john.verification_token == 'faked-uuid4-verification-token'
+        # and we have registered that we sent a token
+        assert john.verification_tokens_sent == 1
+
+    @patch('ow.views.user.send_verification_email')
+    def test_resend_verification_new_token(self, sve, dummy_request, john):
+        john.verified = False
+        john.verification_token = None
+        assert john.verification_tokens_sent == 0
+        response = user_views.resend_verification_link(john, dummy_request)
+        assert isinstance(response, HTTPFound)
+        assert response.location == dummy_request.resource_url(
+            dummy_request.root, 'login',
+            query={'message': 'link-sent', 'email': john.email}
+        )
+        # no emails have been sent
+        sve.assert_called_once_with(dummy_request, john)
+        # the token has been modified
+        assert john.verification_token is not None
+        assert john.verification_tokens_sent == 1
+
+    @patch('ow.views.user.send_verification_email')
+    def test_resend_verification_limit(
+            self, sve, dummy_request, john):
+        john.verified = False
+        john.verification_token = 'faked-uuid4-verification-token'
+        john.verification_tokens_sent = 4
+        response = user_views.resend_verification_link(john, dummy_request)
+        assert isinstance(response, HTTPFound)
+        assert response.location == dummy_request.resource_url(
+            dummy_request.root, 'login',
+            query={'message': 'max-tokens-sent', 'email': john.email}
+        )
+        # no emails have been sent
+        assert not sve.called
+        # the token has not been modified
+        assert john.verification_token == 'faked-uuid4-verification-token'
+        assert john.verification_tokens_sent == 4
+
     def test_dashboard_redirect_unauthenticated(self, root):
         """
-        Anoymous access to the root object, send the user to the login page.
+        Anonymous access to the root object, send the user to the login page.
 
         Instead of reusing the DummyRequest from the request fixture, we do
@@ -400,4 +468,5 @@
         assert response['password'] == ''
         assert response['redirect_url'] == request.resource_url(request.root)
+        assert response['resend_verify_link'] is None
 
     def test_login_get_return_to(self, dummy_request, john):
@@ -413,4 +482,40 @@
         response = user_views.login(request.root, request)
         assert response['redirect_url'] == workout_url
+
+    def test_login_get_GET_messages(self, dummy_request):
+        """
+        GET request to access the login page, passing as a GET parameter
+        a "message key" so a given message is shown to the user
+        """
+        request = dummy_request
+        # first try with the keys we know there are messages associated to
+        request.GET['message'] = 'already-verified'
+        response = user_views.login(request.root, request)
+        assert response['message'] == 'User has been verified already'
+        request.GET['message'] = 'link-sent'
+        response = user_views.login(request.root, request)
+        assert response['message'] == (
+            'Verification link sent, please check your inbox')
+        request.GET['message'] = 'max-tokens-sent'
+        response = user_views.login(request.root, request)
+        assert response['message'] == (
+            'We already sent you the verification link more than three times')
+
+        # now try with a key that has no message assigned
+        request.GET['message'] = 'invalid-message-key'
+        response = user_views.login(request.root, request)
+        assert response['message'] == ''
+
+    def test_login_get_GET_email(self, dummy_request):
+        """
+        GET request to access the login page, passing as a GET parameter
+        an email address. That email is used to automatically fill in the
+        email input in the login form.
+        """
+        request = dummy_request
+        # first try with the keys we know there are messages associated to
+        request.GET['email'] = 'user@example.net'
+        response = user_views.login(request.root, request)
+        assert response['email'] == 'user@example.net'
 
     def test_login_post_wrong_email(self, dummy_request):
Index: ow/views/user.py
===================================================================
--- ow/views/user.py	(revision 38171c6682d26c4ff2094474bec5982c07218687)
+++ ow/views/user.py	(revision 8bae554b97b0673cf21e50e15b41dfc5223a45b1)
@@ -50,9 +50,22 @@
     renderer='ow:templates/login.pt')
 def login(context, request):
-    message = ''
-    email = ''
+    # messages is a dict of pre-defined messages we would need to show to the
+    # user when coming back to the login page after certain actions
+    messages = {
+        'already-verified': _('User has been verified already'),
+        'link-sent': _('Verification link sent, please check your inbox'),
+        'max-tokens-sent': _(
+            'We already sent you the verification link more than three times')
+    }
+    message = request.GET.get('message', '')
+    if message:
+        message = messages.get(message, '')
+    email = request.GET.get('email', '')
     password = ''
     return_to = request.params.get('return_to')
     redirect_url = return_to or request.resource_url(request.root)
+    # If the user still has to verify the account, this will be set to the
+    # proper link to re-send the verification email
+    resend_verify_link = None
 
     if 'submit' in request.POST:
@@ -70,4 +83,7 @@
             else:
                 message = _('You have to verify your account first')
+                resend_verify_link = request.resource_url(
+                    user, 'resend-verification-link'
+                )
         else:
             message = _('Wrong email address')
@@ -77,5 +93,6 @@
         'email': email,
         'password': password,
-        'redirect_url': redirect_url
+        'redirect_url': redirect_url,
+        'resend_verify_link': resend_verify_link
     }
 
@@ -103,4 +120,5 @@
         # send a verification link to the user email address
         send_verification_email(request, user)
+        user.verification_tokens_sent += 1
         # Send to login
         return HTTPFound(location=request.resource_url(context))
@@ -135,4 +153,31 @@
     # if we can not verify the user, show a page with some info about it
     return {}
+
+
+@view_config(
+    context=User,
+    name="resend-verification-link")
+def resend_verification_link(context, request):
+    """
+    Send an email with the verification link, only if the user has not
+    been verified yet
+    """
+    # the message to be shown when the user gets back to the login page
+    query = {'message': 'already-verified'}
+    if not context.verified:
+        tokens_sent = getattr(context, 'verification_tokens_sent', 0)
+        if tokens_sent > 3:
+            # we already sent the token 3 times, we don't send it anymore
+            query = {'message': 'max-tokens-sent', 'email': context.email}
+        else:
+            if context.verification_token is None:
+                # for some reason the verification token is not there, get one
+                context.verification_token = get_verification_token()
+            send_verification_email(request, context)
+            context.verification_tokens_sent = tokens_sent + 1
+            query = {'message': 'link-sent', 'email': context.email}
+    # Send to login
+    url = request.resource_url(request.root, 'login', query=query)
+    return HTTPFound(location=url)
 
 
