diff --git a/browserid/static/dialog/qunit.html b/browserid/static/dialog/qunit.html
index 108cc60fd8fbc23e0105384c865a527e58e19b92..a4baede7d5e5bf3d447ce11f8d51c7901a372a71 100644
--- a/browserid/static/dialog/qunit.html
+++ b/browserid/static/dialog/qunit.html
@@ -27,6 +27,11 @@
         </div>
 
       </div>
+      <span id="email"></span>
+      <span id="cannotconfirm" class="error">Cannot confirm</span>
+      <span id="cannotcommunicate" class="error">Cannot communicate</span>
+      <span id="siteinfo" class="error"><span id="origin"></span></span>
+      <span class=".hint">Hint</span>
     </div>
 		<ol id="qunit-tests"></ol>
 		<div id="qunit-test-area"></div>
diff --git a/browserid/static/dialog/resources/network.js b/browserid/static/dialog/resources/network.js
index 0e587f1ce306df486eaf0625ed03a91762895eb2..b447aa2f08353db38d5f928a4f9b66154e25591b 100644
--- a/browserid/static/dialog/resources/network.js
+++ b/browserid/static/dialog/resources/network.js
@@ -293,6 +293,27 @@ BrowserID.Network = (function() {
       });
     },
 
+    /**
+     * Call with a token to prove an email address ownership.
+     * @method completeEmailRegistration
+     * @param {string} token - token proving email ownership.
+     * @param {function} [onSuccess] - Callback to call when complete.  Called 
+     * with one boolean parameter that specifies the validity of the token.
+     * @param {function} [onFailure] - Called on XHR failure.
+     */
+    completeEmailRegistration: function(token, onSuccess, onFailure) {
+      post({
+        url: "/wsapi/complete_email_addition",
+        data: {
+          token: token
+        },
+        success: function(status, textStatus, jqXHR) {
+          if (onSuccess) onSuccess(status.success);
+        },
+        error: onFailure
+      });
+    },
+
     /**
      * Request a password reset for the given email address.
      * @method requestPasswordReset
@@ -337,26 +358,6 @@ BrowserID.Network = (function() {
       }
     },
 
-    /**
-     * Call with a token to prove an email address ownership.
-     * @method completeEmailRegistration
-     * @param {string} token - token proving email ownership.
-     * @param {function} [onSuccess] - Callback to call when complete.  Called 
-     * with one boolean parameter that specifies the validity of the token.
-     * @param {function} [onFailure] - Called on XHR failure.
-     */
-    completeEmailRegistration: function(token, onSuccess, onFailure) {
-      post({
-        url: "/wsapi/complete_email_addition",
-        data: {
-          token: token
-        },
-        success: function(status, textStatus, jqXHR) {
-          if (onSuccess) onSuccess(status.success);
-        },
-        error: onFailure
-      });
-    },
 
     /**
      * Cancel the current user"s account.
diff --git a/browserid/static/dialog/resources/user.js b/browserid/static/dialog/resources/user.js
index c5117db0399743dc12d69e6c41e21279e0f43f6d..f3740d643b6f8119e3fcd00a23cd1a752043ea29 100644
--- a/browserid/static/dialog/resources/user.js
+++ b/browserid/static/dialog/resources/user.js
@@ -277,7 +277,7 @@ BrowserID.User = (function() {
      * identity.
      * @method cancelUser
      * @param {function} [onSuccess] - Called whenever complete.
-     * @param {function} [onFailure] - called on failure.
+     * @param {function} [onFailure] - called on error.
      */
     cancelUser: function(onSuccess, onFailure) {
       network.cancelUser(function() {
@@ -293,7 +293,7 @@ BrowserID.User = (function() {
      * Log the current user out.
      * @method logoutUser
      * @param {function} [onSuccess] - Called whenever complete.
-     * @param {function} [onFailure] - called on failure.
+     * @param {function} [onFailure] - called on error.
      */
     logoutUser: function(onSuccess, onFailure) {
       network.logout(function() {
@@ -309,7 +309,7 @@ BrowserID.User = (function() {
      * be called.
      * @method syncEmails
      * @param {function} [onSuccess] - Called whenever complete.
-     * @param {function} [onFailure] - Called on failure.
+     * @param {function} [onFailure] - Called on error.
      */
     syncEmails: function(onSuccess, onFailure) {
       cleanupIdentities();
@@ -365,7 +365,7 @@ BrowserID.User = (function() {
      * @param {function} [onSuccess] - Called when check is complete with one 
      * boolean parameter, authenticated.  authenticated will be true if user is 
      * authenticated, false otw.
-     * @param {function} [onFailure] - Called on failure.
+     * @param {function} [onFailure] - Called on error.
      */
     checkAuthentication: function(onSuccess, onFailure) {
       network.checkAuth(function(authenticated) {
@@ -384,7 +384,7 @@ BrowserID.User = (function() {
      * but before sync starts.  Useful for displaying status messages about the 
      * sync taking a moment.
      * @param {function} [onComplete] - Called on sync completion.
-     * @param {function} [onFailure] - Called on failure.
+     * @param {function} [onFailure] - Called on error.
      */
     checkAuthenticationAndSync: function(onSuccess, onComplete, onFailure) {
       var self=this;
@@ -413,7 +413,7 @@ BrowserID.User = (function() {
      * @param {string} email - Email address to authenticate.
      * @param {string} password - Password.
      * @param {function} [onComplete] - Called on sync completion.
-     * @param {function} [onFailure] - Called on failure.
+     * @param {function} [onFailure] - Called on error.
      */
     authenticate: function(email, password, onComplete, onFailure) {
       var self=this;
@@ -452,6 +452,7 @@ BrowserID.User = (function() {
       var self = this;
       network.addEmail(email, origin, function(added) {
         if (added) {
+          localStorage.initiatingOrigin = self.getHostname();
           // we no longer send the keypair, since we will certify it later.
           if (onSuccess) {
             onSuccess(added);
@@ -471,12 +472,42 @@ BrowserID.User = (function() {
       registrationPoll(network.checkEmailRegistration, email, onSuccess, onFailure);
     },
 
+    /**
+     * Verify a users email address given by the token
+     * @method verifyEmail
+     * @param {string} token
+     * @param {function} [onSuccess] - Called on success.
+     *   Called with an object with valid, email, and origin if valid, called 
+     *   with only valid otw.
+     * @param {function} [onFailure] - Called on error.
+     */
+    verifyEmail: function(token, onSuccess, onFailure) {
+      network.emailForVerificationToken(token, function (email) {
+        var invalidInfo = { valid: false };
+        if (email) {
+          network.completeEmailRegistration(token, function (valid) {
+            var info = valid ? {
+              valid: valid,
+              email: email,
+              origin: localStorage.initiatingOrigin
+            } : invalidInfo;
+
+            localStorage.removeItem("initiatingOrigin");
+
+            if (onSuccess) onSuccess(info);
+          }, onFailure);
+        } else if(onSuccess) {
+          onSuccess(invalidInfo);
+        }
+      }, onFailure);
+    },
+
     /**
      * Remove an email address.
      * @method removeEmail
      * @param {string} email - Email address to remove.
      * @param {function} [onSuccess] - Called when complete.
-     * @param {function} [onFailure] - Called on failure.
+     * @param {function} [onFailure] - Called on error.
      */
     removeEmail: function(email, onSuccess, onFailure) {
       if(storage.getEmail(email)) {
@@ -513,7 +544,7 @@ BrowserID.User = (function() {
      * @method getAssertion
      * @param {string} email - Email to get assertion for.
      * @param {function} [onSuccess] - Called with assertion on success.
-     * @param {function} [onFailure] - Called on failure.
+     * @param {function} [onFailure] - Called on error.
      */
     getAssertion: function(email, onSuccess, onFailure) {
       // we use the current time from the browserid servers
diff --git a/browserid/static/dialog/test/qunit/pages/add_email_address_test.js b/browserid/static/dialog/test/qunit/pages/add_email_address_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..bbeb897bd53a3e35c8d774232ff26aee3480cfdf
--- /dev/null
+++ b/browserid/static/dialog/test/qunit/pages/add_email_address_test.js
@@ -0,0 +1,111 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+steal.plugins("jquery").then("/js/pages/add_email_address", function() {
+  "use strict";
+
+  var bid = BrowserID,
+      network = bid.Network,
+      emailForVerificationTokenFailure = false,
+      completeEmailRegistrationFailure = false,
+      validToken = true;
+  
+  var netMock = {
+    emailForVerificationToken: function(token, onSuccess, onFailure) {
+      emailForVerificationTokenFailure ? onFailure() : onSuccess("testuser@testuser.com");
+    },
+
+    completeEmailRegistration: function(token, onSuccess, onFailure) {
+      completeEmailRegistrationFailure ? onFailure() : onSuccess(validToken);
+    }
+  };
+
+  module("pages/add_email_address", {
+    setup: function() {
+      BrowserID.User.setNetwork(netMock);  
+      emailForVerificationTokenFailure = completeEmailRegistrationFailure = false;
+      validToken = true;
+      $(".error").stop().hide();
+      $("#origin").text("");
+    },
+    teardown: function() {
+      BrowserID.User.setNetwork(network);  
+      $(".error").stop().hide();
+      $("#origin").text("");
+    }
+  });
+
+  test("addEmailAddress with good token and site", function() {
+    localStorage.initiatingOrigin = "browserid.org";
+
+    bid.addEmailAddress("token");
+    
+    equal($("#email").text(), "testuser@testuser.com", "email set");
+    ok($("#siteinfo").is(":visible"), "siteinfo is visible when we say what it is");
+    equal($("#origin").text(), "browserid.org", "origin is updated");
+  });
+
+  test("addEmailAddress with good token and nosite", function() {
+    bid.addEmailAddress("token");
+    
+    equal($("#email").text(), "testuser@testuser.com", "email set");
+    equal($("#siteinfo").is(":visible"), false, "siteinfo is not visible without having it");
+    equal($("#origin").text(), "", "origin is not updated");
+  });
+
+  test("addEmailAddress with bad token", function() {
+    validToken = false;
+
+    bid.addEmailAddress("token");
+    ok($("#cannotconfirm").is(":visible"), "cannot confirm box is visible");
+  });
+
+  test("addEmailAddress with emailForVerficationToken XHR failure", function() {
+    validToken = true;
+    emailForVerificationTokenFailure = true;
+    bid.addEmailAddress("token");
+
+    ok($("#cannotcommunicate").is(":visible"), "cannot communicate box is visible");
+  });
+
+  test("addEmailAddress with completeEmailRegistration XHR failure", function() {
+    validToken = true;
+    completeEmailRegistrationFailure = true;
+    bid.addEmailAddress("token");
+
+    ok($("#cannotcommunicate").is(":visible"), "cannot communicate box is visible");
+  });
+});
diff --git a/browserid/static/dialog/test/qunit/qunit.js b/browserid/static/dialog/test/qunit/qunit.js
index 435935bdb970b11177eb07a37efc7aaa60a63e99..1733ac5548a0e89a31aa0f86527d287d7fc4d2ca 100644
--- a/browserid/static/dialog/test/qunit/qunit.js
+++ b/browserid/static/dialog/test/qunit/qunit.js
@@ -13,6 +13,7 @@ steal("/dialog/resources/browserid.js",
 	.views('testBodyTemplate.ejs')
 	.views('wait.ejs')
   .then("browserid_unit_test")
+  .then("pages/add_email_address_test")
   .then("controllers/page_controller_unit_test")
   .then("resources/validation_unit_test")
   .then("resources/storage_unit_test")
diff --git a/browserid/static/dialog/test/qunit/resources/user_unit_test.js b/browserid/static/dialog/test/qunit/resources/user_unit_test.js
index 6f980c1d627340c6b153f900221dcf698bf0a27f..4ee2445e4b70762eb2ba6bb94d2c42fdb03c082c 100644
--- a/browserid/static/dialog/test/qunit/resources/user_unit_test.js
+++ b/browserid/static/dialog/test/qunit/resources/user_unit_test.js
@@ -56,7 +56,8 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio
       userCheckCount = 0,
       emailCheckCount = 0,
       registrationResponse,
-      xhrFailure = false; 
+      xhrFailure = false,
+      validToken = true; 
 
   var netStub = {
     reset: function() {
@@ -98,6 +99,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio
       xhrFailure ? onFailure() : onSuccess(status);
     },
 
+    emailForVerificationToken: function(token, onSuccess, onFailure) {
+      xhrFailure ? onFailure() : onSuccess("testuser@testuser.com");
+    },
+
+    completeEmailRegistration: function(token, onSuccess, onFailure) {
+      xhrFailure ? onFailure() : onSuccess(validToken);
+    },
+
     removeEmail: function(email, onSuccess, onFailure) {
       xhrFailure ? onFailure() : onSuccess();
     },
@@ -200,6 +209,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio
       netStub.reset();
       userCheckCount = 0;
       emailCheckCount = 0;
+      validToken = true;
     },
     teardown: function() {
       lib.setNetwork(BrowserID.Network);
@@ -528,6 +538,9 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio
       var identities = lib.getStoredEmailKeypairs();
       equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation.");
 
+
+      equal(localStorage.initiatingOrigin, lib.getHostname(), "initiatingOrigin is stored"); 
+
       start();
     }, failure("addEmail failure"));
 
@@ -607,6 +620,49 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/user", functio
   });
 
 
+  test("verifyEmail with a good token", function() {
+    localStorage.initiatingOrigin = "browserid.org";
+    lib.verifyEmail("token", function onSuccess(info) {
+      
+      ok(info.valid, "token was valid");
+      equal(info.email, "testuser@testuser.com", "email part of info");
+      equal(info.origin, "browserid.org", "origin in info");
+      equal(localStorage.initiatingOrigin, null, "initiating origin was removed");
+
+      start();
+    }, failure("verifyEmail failure"));
+
+    stop();
+  });
+
+  test("verifyEmail with a bad token", function() {
+    validToken = false;
+
+    lib.verifyEmail("token", function onSuccess(info) {
+      
+      equal(info.valid, false, "bad token calls onSuccess with a false validity");
+
+      start();
+    }, failure("verifyEmail failure"));
+
+    stop();
+
+  });
+
+  test("verifyEmail with an XHR failure", function() {
+    xhrFailure = true;
+
+    lib.verifyEmail("token", function onSuccess(info) {
+      ok(false, "xhr failure should never succeed");
+      start();
+    }, function() {
+      ok(true, "xhr failure should always be a failure"); 
+      start();
+    });
+      
+    stop();
+  });
+
   test("syncEmailKeypair with successful sync", function() {
     syncValid = true;
     lib.syncEmailKeypair("testemail@testemail.com", function(keypair) {
diff --git a/browserid/static/js/pages/add_email_address.js b/browserid/static/js/pages/add_email_address.js
index e5c91d305cadf9e79c50636fd36154abce90db4b..b1ab0fde7a048cef6f326ac9dd9e0c5c50fc80fe 100644
--- a/browserid/static/js/pages/add_email_address.js
+++ b/browserid/static/js/pages/add_email_address.js
@@ -36,28 +36,34 @@
 
 (function() {
   "use strict";
-
-  function emailRegistrationSuccess() {
+  
+  var ANIMATION_TIME=250;
+  function emailRegistrationSuccess(info) {
     $(".hint").hide();
-    $("#congrats").fadeIn(250, function() {
-      $("body").delay(1000).fadeOut(500, function() {
-        // if the close didn't work, then let's redirect the the main page where they'll
-        // get to see the ids that they've created.
-        document.location = '/';
-      });
-    });
+
+    $("#email").text(info.email);
+    
+    if (info.origin) {
+      $("#origin").text(info.origin);
+      $("#siteinfo").show();
+    }
+
+    $("#congrats").fadeIn(ANIMATION_TIME);
   }
 
   function showError(el) {
     $(".hint").hide();
-    $(el).fadeIn(250);
+    $(el).fadeIn(ANIMATION_TIME);
   }
 
   BrowserID.addEmailAddress = function(token) {
-    BrowserID.Network.completeEmailRegistration(token, function onSuccess(valid) {
-      if (valid) {
-        emailRegistrationSuccess();
-      } else {
+    var user = BrowserID.User;
+
+    user.verifyEmail(token, function onSuccess(info) {
+      if (info.valid) {
+        emailRegistrationSuccess(info);
+      }
+      else {
         showError("#cannotconfirm");
       }
     }, function onFailure() {
diff --git a/browserid/views/verifyemail.ejs b/browserid/views/verifyemail.ejs
index 766c84b76d2a307a6108f6c865e08411d381ded3..83d3ffbf5e2b94b7aceb5a64df6afa971dcd98ea 100644
--- a/browserid/views/verifyemail.ejs
+++ b/browserid/views/verifyemail.ejs
@@ -11,7 +11,14 @@
         
 
         <div id="congrats" class="serif">
-          Your email address has been verified!
+          <p>
+            <strong id="email">Your address</strong> has been verified!
+
+            <span id="siteinfo">
+              Your new address has been used to sign in to <strong id="origin"></strong>, 
+              which will be in its original window or tab.
+            </span>
+          </p>
         </div>
   </div>
 </div>