#4242 Support multi emails register
diff --git a/acme/acme/messages.py b/acme/acme/messages.py
index 03dbc325579..827a4dd11a6 100644
--- a/acme/acme/messages.py
+++ b/acme/acme/messages.py
@@ -285,7 +285,7 @@ def from_data(cls, phone=None, email=None, **kwargs):
         if phone is not None:
             details.append(cls.phone_prefix + phone)
         if email is not None:
-            details.append(cls.email_prefix + email)
+            details.extend([cls.email_prefix + mail for mail in email.split(',')])
         kwargs['contact'] = tuple(details)
         return cls(**kwargs)
 
diff --git a/certbot/cli.py b/certbot/cli.py
index b71d60055a0..bd39362bc9f 100644
--- a/certbot/cli.py
+++ b/certbot/cli.py
@@ -418,7 +418,7 @@ def _get_help_string(self, action):
     }),
     ("enhance", {
         "short": "Add security enhancements to your existing configuration",
-        "opts": ("Helps to harden the TLS configration by adding security enhancements "
+        "opts": ("Helps to harden the TLS configuration by adding security enhancements "
                  "to already existing configuration."),
         "usage": "\n\n  certbot enhance [options]\n\n"
     }),
diff --git a/certbot/client.py b/certbot/client.py
index 45dc9c63b9e..59514f8d139 100644
--- a/certbot/client.py
+++ b/certbot/client.py
@@ -179,8 +179,9 @@ def perform_registration(acme, config, tos_cb):
     Actually register new account, trying repeatedly if there are email
     problems
 
-    :param .IConfig config: Client configuration.
     :param acme.client.Client client: ACME client object.
+    :param .IConfig config: Client configuration.
+    :param Callable tos_cb: a callback to handle Term of Service agreement.
 
     :returns: Registration Resource.
     :rtype: `acme.messages.RegistrationResource`
diff --git a/certbot/interfaces.py b/certbot/interfaces.py
index c96f6bd51f2..6233e35929a 100644
--- a/certbot/interfaces.py
+++ b/certbot/interfaces.py
@@ -201,7 +201,9 @@ class IConfig(zope.interface.Interface):
     """
     server = zope.interface.Attribute("ACME Directory Resource URI.")
     email = zope.interface.Attribute(
-        "Email used for registration and recovery contact. (default: Ask)")
+        "Email used for registration and recovery contact. Use comma to "
+        "register multiple emails, ex: u1@example.com,u2@example.com. "
+        "(default: Ask).")
     rsa_key_size = zope.interface.Attribute("Size of the RSA key.")
     must_staple = zope.interface.Attribute(
         "Adds the OCSP Must Staple extension to the certificate. "
diff --git a/certbot/main.py b/certbot/main.py
index a041b998f98..c6247e762e2 100644
--- a/certbot/main.py
+++ b/certbot/main.py
@@ -483,6 +483,21 @@ def _determine_account(config):
     :raises errors.Error: If unable to register an account with ACME server
 
     """
+    def _tos_cb(terms_of_service):
+        if config.tos:
+            return True
+        msg = ("Please read the Terms of Service at {0}. You "
+               "must agree in order to register with the ACME "
+               "server at {1}".format(
+                   terms_of_service, config.server))
+        obj = zope.component.getUtility(interfaces.IDisplay)
+        result = obj.yesno(msg, "Agree", "Cancel",
+                         cli_flag="--agree-tos", force_interactive=True)
+        if not result:
+            raise errors.Error(
+                "Registration cannot proceed without accepting "
+                "Terms of Service.")
+
     account_storage = account.AccountFileStorage(config)
     acme = None
 
@@ -497,21 +512,6 @@ def _determine_account(config):
         else:  # no account registered yet
             if config.email is None and not config.register_unsafely_without_email:
                 config.email = display_ops.get_email()
-
-            def _tos_cb(terms_of_service):
-                if config.tos:
-                    return True
-                msg = ("Please read the Terms of Service at {0}. You "
-                       "must agree in order to register with the ACME "
-                       "server at {1}".format(
-                           terms_of_service, config.server))
-                obj = zope.component.getUtility(interfaces.IDisplay)
-                result = obj.yesno(msg, "Agree", "Cancel",
-                                 cli_flag="--agree-tos", force_interactive=True)
-                if not result:
-                    raise errors.Error(
-                        "Registration cannot proceed without accepting "
-                        "Terms of Service.")
             try:
                 acc, acme = client.register(
                     config, account_storage, tos_cb=_tos_cb)
@@ -731,8 +731,9 @@ def register(config, unused_plugins):
     acc, acme = _determine_account(config)
     cb_client = client.Client(config, acc, None, None, acme=acme)
     # We rely on an exception to interrupt this process if it didn't work.
+    acc_contacts = ['mailto:' + email for email in config.email.split(',')]
     acc.regr = cb_client.acme.update_registration(acc.regr.update(
-        body=acc.regr.body.update(contact=('mailto:' + config.email,))))
+        body=acc.regr.body.update(contact=acc_contacts)))
     account_storage.save_regr(acc, cb_client.acme)
     eff.handle_subscription(config)
     add_msg("Your e-mail address was updated to {0}.".format(config.email))
diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py
index 22653ca3aba..68a068973a1 100644
--- a/certbot/tests/main_test.py
+++ b/certbot/tests/main_test.py
@@ -1433,7 +1433,9 @@ def test_update_registration_with_email(self, mock_utility, mock_email):
                         mocked_storage = mock.MagicMock()
                         mocked_account.AccountFileStorage.return_value = mocked_storage
                         mocked_storage.find_all.return_value = ["an account"]
-                        mocked_det.return_value = (mock.MagicMock(), "foo")
+                        mock_acc = mock.MagicMock()
+                        mock_regr = mock_acc.regr
+                        mocked_det.return_value = (mock_acc, "foo")
                         cb_client = mock.MagicMock()
                         mocked_client.Client.return_value = cb_client
                         x = self._call_no_clientmock(
@@ -1443,8 +1445,10 @@ def test_update_registration_with_email(self, mock_utility, mock_email):
                         self.assertTrue(x[0] is None)
                         # and we got supposedly did update the registration from
                         # the server
-                        self.assertTrue(
-                            cb_client.acme.update_registration.called)
+                        reg_arg = cb_client.acme.update_registration.call_args[0][0]
+                        # Test the return value of .update() was used because
+                        # the regr is immutable.
+                        self.assertEqual(reg_arg, mock_regr.update())
                         # and we saved the updated registration on disk
                         self.assertTrue(mocked_storage.save_regr.called)
                         self.assertTrue(
diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh
index 9748befa383..e931e30f3c5 100755
--- a/tests/boulder-integration.sh
+++ b/tests/boulder-integration.sh
@@ -191,7 +191,14 @@ for dir in $renewal_hooks_dirs; do
         exit 1
     fi
 done
-common register --update-registration --email example@example.org
+
+common unregister
+
+common register --email ex1@domain.org,ex2@domain.org
+
+common register --update-registration --email ex1@domain.org
+
+common register --update-registration --email ex1@domain.org,ex2@domain.org
 
 common plugins --init --prepare | grep webroot
 

This change will allow registering/updating account with multi emails. 
Detail is enclosed in #4242 