diff --git a/reviewboard/hostingsvcs/hook_utils.py b/reviewboard/hostingsvcs/hook_utils.py
index e0f6ea71669e65439369eb263cf947e024b9f2e8..fcf6764c9395e4f78f0ec676e5d1183842c0d843 100644
--- a/reviewboard/hostingsvcs/hook_utils.py
+++ b/reviewboard/hostingsvcs/hook_utils.py
@@ -74,8 +74,26 @@ def get_review_request_id(commit_message, server_url, commit_id=None,
     return review_request_id
 
 
-def close_review_request(review_request, review_request_id, description):
-    """Closes the specified review request as submitted."""
+def close_review_request(review_request, review_request_id, description,
+                         automated=None):
+    """Close the specified review request as submitted.
+
+    This will close the specific review request that is published.
+
+    Args:
+        review_request (reviewboard.reviews.models.ReviewRequest):
+            The review request to close.
+
+        review_request_id (int):
+            The ID of the review request.
+
+        description (unicode):
+            The description that indicates the closing of the review request.
+
+        automated (boolean, optional):
+            An optional field that indicates whether or not the review request
+            is closed automatically.
+    """
     if review_request.status == ReviewRequest.SUBMITTED:
         logging.warning('Review request #%s is already submitted.',
                         review_request_id)
@@ -87,7 +105,8 @@ def close_review_request(review_request, review_request_id, description):
         review_request.publish(review_request.submitter, trivial=True,
                                validate_fields=False)
 
-    review_request.close(ReviewRequest.SUBMITTED, description=description)
+    review_request.close(ReviewRequest.SUBMITTED, description=description,
+                         automated=automated)
     logging.debug('Review request #%s is set to %s.',
                   review_request_id, review_request.status)
 
@@ -154,4 +173,5 @@ def close_all_review_requests(review_request_id_to_commits, local_site_name,
             review_request,
             review_request_id,
             ('Pushed to ' +
-             ', '.join(review_request_id_to_commits[review_request_id])))
+             ', '.join(review_request_id_to_commits[review_request_id])),
+            automated=True)
diff --git a/reviewboard/hostingsvcs/tests/test_bitbucket.py b/reviewboard/hostingsvcs/tests/test_bitbucket.py
index 714f2fe33b25d3f835dba9e7da6c78c17d6416ea..24b871338b7eb7915396602abdcfac8239702f88 100644
--- a/reviewboard/hostingsvcs/tests/test_bitbucket.py
+++ b/reviewboard/hostingsvcs/tests/test_bitbucket.py
@@ -1353,7 +1353,9 @@ class CloseSubmittedHookTests(BitbucketTestCase):
         self.assertEqual(review_request.changedescs.count(), 1)
 
         changedesc = review_request.changedescs.get()
+
         self.assertEqual(changedesc.text, 'Pushed to master (1c44b46)')
+        self.assertTrue(changedesc.extra_data.get('automated'))
 
     def _post_commit_hook_payload(self, post_url, review_request_url,
                                   truncated=False):
diff --git a/reviewboard/hostingsvcs/tests/test_github.py b/reviewboard/hostingsvcs/tests/test_github.py
index 55650e0c7156d142fb6384801dc1d11f86c8f87b..e5f6829c076f247e6a2ca2178f683006448056af 100644
--- a/reviewboard/hostingsvcs/tests/test_github.py
+++ b/reviewboard/hostingsvcs/tests/test_github.py
@@ -1532,7 +1532,9 @@ class CloseSubmittedHookTests(GitHubTestCase):
         self.assertEqual(review_request.changedescs.count(), 1)
 
         changedesc = review_request.changedescs.get()
+
         self.assertEqual(changedesc.text, 'Pushed to master (1c44b46)')
+        self.assertTrue(changedesc.extra_data.get('automated'))
 
     def _post_commit_hook_payload(self, post_url, review_request_url, secret,
                                   event='push'):
diff --git a/reviewboard/hostingsvcs/tests/test_rbgateway.py b/reviewboard/hostingsvcs/tests/test_rbgateway.py
index edc27ed8a26467aa280632ee1a5124c3e8ed1570..dfae8f8187249b240ff1b33f93d3c25dc9abe757 100644
--- a/reviewboard/hostingsvcs/tests/test_rbgateway.py
+++ b/reviewboard/hostingsvcs/tests/test_rbgateway.py
@@ -700,7 +700,9 @@ class CloseSubmittedHookTests(ReviewBoardGatewayTestCase):
         self.assertEqual(review_request.changedescs.count(), 1)
 
         changedesc = review_request.changedescs.get()
+
         self.assertEqual(changedesc.text, expected_close_msg)
+        self.assertTrue(changedesc.extra_data.get('automated'))
 
     def _post_commit_hook_payload(self, post_url, repository_name,
                                   review_request_url, secret,
diff --git a/reviewboard/notifications/email/message.py b/reviewboard/notifications/email/message.py
index 45f3e3de816da4c250a0d31f2787020934467ee8..b35405afb20b0442c8848c625e4addfdbd7e41a5 100644
--- a/reviewboard/notifications/email/message.py
+++ b/reviewboard/notifications/email/message.py
@@ -89,10 +89,16 @@ def _get_server_base_url():
     return build_server_url('/')[:-1]
 
 
-def prepare_base_review_request_mail(user, review_request, subject,
-                                     in_reply_to, to_field, cc_field,
-                                     template_name_base, context=None,
-                                     extra_headers=None):
+def prepare_base_review_request_mail(user,
+                                     review_request,
+                                     subject,
+                                     in_reply_to,
+                                     to_field,
+                                     cc_field,
+                                     template_name_base,
+                                     context=None,
+                                     extra_headers=None,
+                                     automated=None):
     """Return a customized review request e-mail.
 
     This is intended to be called by one of the ``prepare_{type}_mail``
@@ -136,15 +142,23 @@ def prepare_base_review_request_mail(user, review_request, subject,
         extra_headers (dict, optional):
             Optional additional headers to include.
 
+        automated (bool, optional):
+            Optional field that indicates whether or not the the review request
+            is published or closed automatically.
+
     Returns:
         EmailMessage:
         The prepared e-mail message.
     """
-    user_email = build_email_address_for_user(user)
+    if automated:
+        user_email = settings.EMAIL_DEFAULT_SENDER_SERVICE_NAME
+    else:
+        user_email = build_email_address_for_user(user)
+
     to_field = recipients_to_addresses(to_field, review_request.id)
     cc_field = recipients_to_addresses(cc_field, review_request.id) - to_field
 
-    if not user.should_send_own_updates():
+    if user and not user.should_send_own_updates():
         to_field.discard(user_email)
         cc_field.discard(user_email)
 
@@ -228,7 +242,7 @@ def prepare_base_review_request_mail(user, review_request, subject,
 
             headers.appendlist('X-ReviewBoard-Diff-For', filename)
 
-    if settings.DEFAULT_FROM_EMAIL:
+    if user and settings.DEFAULT_FROM_EMAIL:
         sender = build_email_address(full_name=user.get_full_name(),
                                      email=settings.DEFAULT_FROM_EMAIL)
     else:
@@ -411,7 +425,7 @@ def prepare_review_published_mail(user, review, review_request, request,
 
 
 def prepare_review_request_mail(user, review_request, changedesc=None,
-                                close_type=None):
+                                close_type=None, automated=None):
     """Return an e-mail representing the supplied review request.
 
     Args:
@@ -437,11 +451,15 @@ def prepare_review_request_mail(user, review_request, changedesc=None,
             * :py:attr:`~reviewboard.reviews.models.ReviewRequest.SUBMITTED`
             * :py:attr:`~reviewboard.reviews.models.ReviewRequest.DISCARDED`
 
+        automated (bool, optional):
+            An optional field that indicates whether or not the review request
+            is published or closed automatically.
+
     Returns:
         EmailMessage:
         The e-mail message representing the review request.
     """
-    if not user:
+    if not (user or automated):
         user = review_request.submitter
 
     summary = _ensure_unicode(review_request.summary)
@@ -476,6 +494,10 @@ def prepare_review_request_mail(user, review_request, changedesc=None,
             'changes': fields_changed,
         })
 
+        extra_context.update({
+            'automated': changedesc.extra_data.get('automated', False),
+        })
+
         if (changed_field_names and
             changed_field_names <= {'target_people', 'target_groups'}):
             # If the only changes are to the target reviewers, try to send a
@@ -504,7 +526,8 @@ def prepare_review_request_mail(user, review_request, changedesc=None,
 
     return prepare_base_review_request_mail(
         user, review_request, subject, reply_message_id, to_field,
-        cc_field, 'notifications/review_request_email', extra_context)
+        cc_field, 'notifications/review_request_email', extra_context,
+        automated=automated)
 
 
 def prepare_user_registered_mail(user):
diff --git a/reviewboard/notifications/email/signal_handlers.py b/reviewboard/notifications/email/signal_handlers.py
index 3919551dd3400581bba9bed47a8aff85b37cc916..c61853e083a80235031d4675cfc5b7dadcde3617 100644
--- a/reviewboard/notifications/email/signal_handlers.py
+++ b/reviewboard/notifications/email/signal_handlers.py
@@ -140,7 +140,7 @@ def send_review_published_mail(user, review, request, to_owner_only,
 
 
 def send_review_request_closed_mail(user, review_request, close_type,
-                                    **kwargs):
+                                    automated, **kwargs):
     """Send e-mail when a review request is closed.
 
     Listens to the
@@ -158,6 +158,10 @@ def send_review_request_closed_mail(user, review_request, close_type,
         close_type (unicode):
             How the review request was closed.
 
+        automated (bool):
+            Indicates whether or not the review request is closed
+            automatically.
+
         **kwargs (dict):
             Unused keyword arguments from the signal.
     """
@@ -170,14 +174,15 @@ def send_review_request_closed_mail(user, review_request, close_type,
     message, sent = send_email(prepare_review_request_mail,
                                user=user,
                                review_request=review_request,
-                               close_type=close_type)
+                               close_type=close_type,
+                               automated=automated)
 
     if sent:
         _update_email_info(review_request, message.message_id)
 
 
 def send_review_request_published_mail(user, review_request, trivial,
-                                       changedesc, **kwargs):
+                                       changedesc, automated, **kwargs):
     """Send e-mail when a review request is published.
 
     Listens to the
@@ -201,6 +206,10 @@ def send_review_request_published_mail(user, review_request, trivial,
         changedesc (reviewboard.changedescs.models.ChangeDescription, optional):
             The change description associated with the publish, if any.
 
+        automated (bool):
+            Indicates whether or not the review request is published
+            automatically.
+
         **kwargs (dict):
             Ignored keyword arguments from the signal.
     """
@@ -216,7 +225,8 @@ def send_review_request_published_mail(user, review_request, trivial,
     message, sent = send_email(prepare_review_request_mail,
                                user=user,
                                review_request=review_request,
-                               changedesc=changedesc)
+                               changedesc=changedesc,
+                               automated=automated)
 
     if sent:
         _update_email_info(review_request, message.message_id)
diff --git a/reviewboard/notifications/email/utils.py b/reviewboard/notifications/email/utils.py
index 793176a5bd033ec18265d543e9f4b468f41d91a2..9565f9d2734709f605d926282eca3e47015e58a1 100644
--- a/reviewboard/notifications/email/utils.py
+++ b/reviewboard/notifications/email/utils.py
@@ -91,7 +91,7 @@ def build_recipients(user, review_request, extra_recipients=None,
     if not extra_recipients:
         extra_recipients = User.objects.none()
 
-    if user.should_send_email():
+    if user and user.should_send_email():
         recipients.add(user)
 
     try:
@@ -169,7 +169,7 @@ def build_recipients(user, review_request, extra_recipients=None,
         recipients.update(to_field)
         recipients.update(review_request.target_groups.all())
 
-    if not user.should_send_own_updates():
+    if user and not user.should_send_own_updates():
         recipients.discard(user)
         to_field.discard(user)
 
diff --git a/reviewboard/notifications/tests/test_email_sending.py b/reviewboard/notifications/tests/test_email_sending.py
index 354648b2b0c67a488fc50cfa06ce718d0d88bf0e..de0bd7a40b718bdcc7f08140f11ac4e059ee107a 100644
--- a/reviewboard/notifications/tests/test_email_sending.py
+++ b/reviewboard/notifications/tests/test_email_sending.py
@@ -19,8 +19,12 @@ from reviewboard.accounts.models import ReviewRequestVisit
 from reviewboard.admin.server import build_server_url, get_server_url
 from reviewboard.admin.siteconfig import load_site_config, settings_map
 from reviewboard.diffviewer.models import FileDiff
+from reviewboard.notifications.email.signal_handlers import (
+    send_review_request_closed_mail,
+    send_review_request_published_mail)
 from reviewboard.notifications.email.message import (
     prepare_base_review_request_mail,
+    prepare_review_request_mail,
     logger as messageLogger)
 from reviewboard.notifications.email.utils import (
     get_email_addresses_for_group,
@@ -707,6 +711,90 @@ class ReviewRequestEmailTests(ReviewRequestEmailTestsMixin, DmarcDnsTestsMixin,
             self.assertTrue('This change has been marked as submitted'
                             in message.as_string())
 
+    def test_review_request_closed_with_email_and_not_automated(self):
+        """Testing e-mail is generated when a review request is not closed
+        automatically (without automated=True) and e-mail setting is True
+        """
+        with self.siteconfig_settings({'mail_send_review_close_mail': True},
+                                      reload_settings=False):
+            review_request = self.create_review_request()
+            review_request.publish(review_request.submitter)
+
+            # Clear the outbox.
+            mail.outbox = []
+
+            self.spy_on(send_review_request_closed_mail)
+            self.spy_on(prepare_review_request_mail)
+            self.spy_on(prepare_base_review_request_mail)
+
+            review_request.close(ReviewRequest.SUBMITTED,
+                                 review_request.submitter,
+                                 description='Test without automated')
+
+            from_email = build_email_address_for_user(review_request.submitter)
+
+            self.assertEqual(len(mail.outbox), 1)
+            self.assertEqual(mail.outbox[0].from_email, self.sender)
+            self.assertEqual(mail.outbox[0].extra_headers['From'], from_email)
+
+            message = mail.outbox[0].message()
+            self.assertIn('Thanks', message.as_string())
+
+            self.assertSpyCalledWith(send_review_request_closed_mail,
+                                     user=review_request.submitter,
+                                     review_request=review_request,
+                                     close_type=ReviewRequest.SUBMITTED,
+                                     automated=None)
+            self.assertSpyCalledWith(prepare_review_request_mail,
+                                     user=review_request.submitter,
+                                     review_request=review_request,
+                                     close_type=ReviewRequest.SUBMITTED,
+                                     automated=None)
+            self.assertSpyCalled(prepare_base_review_request_mail)
+            self.assertFalse(prepare_base_review_request_mail.last_call.kwargs
+                             ['context'].get('automated'))
+
+    def test_review_request_closed_with_email_and_automated(self):
+        """Testing e-mail is generated when a review request is closed
+        automatically (automated=True) and e-mail setting is True
+        """
+        with self.siteconfig_settings({'mail_send_review_close_mail': True},
+                                      reload_settings=False):
+            review_request = self.create_review_request()
+            review_request.publish(review_request.submitter)
+
+            # Clear the outbox.
+            mail.outbox = []
+
+            self.spy_on(send_review_request_closed_mail)
+            self.spy_on(prepare_review_request_mail)
+            self.spy_on(prepare_base_review_request_mail)
+
+            review_request.close(ReviewRequest.SUBMITTED,
+                                 description='Test with automated',
+                                 automated=True)
+
+            self.assertEqual(len(mail.outbox), 1)
+            self.assertEqual(mail.outbox[0].extra_headers['From'],
+                             settings.EMAIL_DEFAULT_SENDER_SERVICE_NAME)
+
+            message = mail.outbox[0].message()
+            self.assertNotIn('Thanks', message.as_string())
+
+            self.assertSpyCalledWith(send_review_request_closed_mail,
+                                     user=None,
+                                     review_request=review_request,
+                                     close_type=ReviewRequest.SUBMITTED,
+                                     automated=True)
+            self.assertSpyCalledWith(prepare_review_request_mail,
+                                     user=None,
+                                     review_request=review_request,
+                                     close_type=ReviewRequest.SUBMITTED,
+                                     automated=True)
+            self.assertSpyCalled(prepare_base_review_request_mail)
+            self.assertTrue(prepare_base_review_request_mail.last_call.kwargs
+                            ['context'].get('automated'))
+
     def test_review_request_close_with_email_and_dmarc_deny(self):
         """Tests e-mail is generated when a review request is closed and
         e-mail setting is True and From spoofing blocked by DMARC
@@ -882,6 +970,116 @@ class ReviewRequestEmailTests(ReviewRequestEmailTestsMixin, DmarcDnsTestsMixin,
         self.assertEqual(message['Sender'],
                          self._get_sender(review_request.submitter))
 
+    def test_update_review_request_email_with_automated(self):
+        """Testing sending an e-mail when a review request is updated
+        automatically (automated=True)
+        """
+        group = Group.objects.create(name='devgroup',
+                                     mailing_list='devgroup@example.com')
+
+        review_request = self.create_review_request(
+            summary='My test review request', publish=True)
+        review_request.target_groups.add(group)
+
+        draft = ReviewRequestDraft.create(review_request)
+        draft.summary = 'A new summary'
+        draft.save()
+
+        self.spy_on(send_review_request_published_mail)
+        self.spy_on(prepare_review_request_mail)
+        self.spy_on(prepare_base_review_request_mail)
+
+        # Since we're only testing for the draft's publish e-mail,
+        # clear the outbox.
+        mail.outbox = []
+
+        review_request.publish(review_request.submitter, automated=True)
+
+        changedesc = review_request.changedescs.latest()
+
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(mail.outbox[0].extra_headers['From'],
+                         settings.EMAIL_DEFAULT_SENDER_SERVICE_NAME)
+        self.assertEqual(mail.outbox[0].subject,
+                         'Re: Review Request %s: A new summary'
+                         % review_request.pk)
+        self.assertValidRecipients([review_request.submitter.username],
+                                   ['devgroup'])
+
+        message = mail.outbox[0].message()
+        self.assertNotIn('Thanks', message.as_string())
+
+        self.assertSpyCalledWith(send_review_request_published_mail,
+                                 user=review_request.submitter,
+                                 review_request=review_request,
+                                 trivial=False,
+                                 changedesc=changedesc,
+                                 automated=True)
+        self.assertSpyCalledWith(prepare_review_request_mail,
+                                 user=review_request.submitter,
+                                 review_request=review_request,
+                                 changedesc=changedesc,
+                                 automated=True)
+        self.assertSpyCalled(prepare_base_review_request_mail)
+        self.assertTrue(prepare_base_review_request_mail.last_call.kwargs
+                        ['context'].get('automated'))
+
+    def test_update_review_request_email_not_automated(self):
+        """Testing sending an e-mail when a review request is not updated
+        automatically (without automated=True)
+        """
+        group = Group.objects.create(name='devgroup',
+                                     mailing_list='devgroup@example.com')
+
+        review_request = self.create_review_request(
+            summary='My test review request', publish=True)
+        review_request.target_groups.add(group)
+
+        draft = ReviewRequestDraft.create(review_request)
+        draft.summary = 'A new summary'
+        draft.save()
+
+        self.spy_on(send_review_request_published_mail)
+        self.spy_on(prepare_review_request_mail)
+        self.spy_on(prepare_base_review_request_mail)
+
+        # Since we're only testing for the draft's publish e-mail,
+        # clear the outbox.
+        mail.outbox = []
+
+        review_request.publish(review_request.submitter)
+
+        from_email = build_email_address_for_user(review_request.submitter)
+
+        changedesc = review_request.changedescs.latest()
+
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(mail.outbox[0].extra_headers['From'],
+                         from_email)
+        self.assertEqual(mail.outbox[0].subject,
+                         'Re: Review Request %s: A new summary'
+                         % review_request.pk)
+        self.assertValidRecipients([review_request.submitter.username],
+                                   ['devgroup'])
+
+        message = mail.outbox[0].message()
+        self.assertIn('Thanks', message.as_string())
+
+        self.assertSpyCalledWith(send_review_request_published_mail,
+                                 user=review_request.submitter,
+                                 review_request=review_request,
+                                 trivial=False,
+                                 changedesc=changedesc,
+                                 automated=None)
+        self.assertSpyCalledWith(prepare_review_request_mail,
+                                 user=review_request.submitter,
+                                 review_request=review_request,
+                                 changedesc=changedesc,
+                                 automated=None)
+        self.assertSpyCalled(prepare_base_review_request_mail)
+        self.assertFalse(prepare_base_review_request_mail.last_call.kwargs
+                         ['context'].get('automated'))
+
     def test_add_reviewer_review_request_email(self):
         """Testing limited e-mail recipients
         when adding a reviewer to an existing review request
diff --git a/reviewboard/reviews/models/review_request.py b/reviewboard/reviews/models/review_request.py
index b00f931b098e00a848cfe1083f02597e11bf1b17..f21f0f1ab5a7f90c35a78821d2c34ef909db8b08 100644
--- a/reviewboard/reviews/models/review_request.py
+++ b/reviewboard/reviews/models/review_request.py
@@ -948,7 +948,7 @@ class ReviewRequest(BaseReviewRequestDetails):
         )
 
     def close(self, close_type=None, user=None, description=None,
-              rich_text=False, **kwargs):
+              rich_text=False, automated=None, **kwargs):
         """Closes the review request.
 
         Args:
@@ -956,16 +956,20 @@ class ReviewRequest(BaseReviewRequestDetails):
                 How the close occurs. This should be one of
                 :py:attr:`SUBMITTED` or :py:attr:`DISCARDED`.
 
-            user (django.contrib.auth.models.User):
+            user (django.contrib.auth.models.User, optional):
                 The user who is closing the review request.
 
-            description (unicode):
+            description (unicode, optional):
                 An optional description that indicates why the review request
                 was closed.
 
             rich_text (bool):
                 Indicates whether or not that the description is rich text.
 
+            automated (bool, optional):
+                An optional field that indicates whether or not the review
+                request is closed automatically.
+
         Raises:
             ValueError:
                 The provided close type is not a valid value.
@@ -1012,7 +1016,8 @@ class ReviewRequest(BaseReviewRequestDetails):
             review_request=self,
             close_type=close_type,
             description=description,
-            rich_text=rich_text)
+            rich_text=rich_text,
+            automated=automated)
 
         draft = get_object_or_none(self.draft)
 
@@ -1028,6 +1033,9 @@ class ReviewRequest(BaseReviewRequestDetails):
                                            rich_text=rich_text or False,
                                            user=user or self.submitter)
 
+            if automated:
+                changedesc.extra_data['automated'] = True
+
             status_field = get_review_request_field('status')(self)
             status_field.record_change_entry(changedesc, self.status,
                                              close_type)
@@ -1050,7 +1058,8 @@ class ReviewRequest(BaseReviewRequestDetails):
                 review_request=self,
                 close_type=close_type,
                 description=description,
-                rich_text=rich_text)
+                rich_text=rich_text,
+                automated=automated)
         else:
             # Update submission description.
             changedesc = self.changedescs.filter(public=True).latest()
@@ -1109,11 +1118,31 @@ class ReviewRequest(BaseReviewRequestDetails):
                                      old_status=old_status,
                                      old_public=old_public)
 
-    def publish(self, user, trivial=False, validate_fields=True):
+    def publish(self, user, trivial=False, validate_fields=True,
+                automated=None):
         """Publishes the current draft attached to this review request.
 
         The review request will be mark as public, and signals will be
         emitted for any listeners.
+
+        Args:
+            user (django.contrib.auth.models.User, optional):
+                The user publishing the draft.
+
+            trivial (bool, optional):
+                Whether or not this is a trivial publish.
+
+                Trivial publishes do not result in e-mail notifications.
+
+            validate_fields (bool, optional):
+                Whether or not the fields should be validated.
+
+                This should only be ``False`` in the case of programmatic
+                publishes, e.g., from close as submitted hooks.
+
+            automated (bool, optional):
+                An optional field that indicates whether or not the review
+                request is published automatically.
         """
         if not self.is_mutable_by(user):
             raise PermissionError
@@ -1156,7 +1185,8 @@ class ReviewRequest(BaseReviewRequestDetails):
             old_submitter.get_site_profile(self.local_site)
 
         review_request_publishing.send(sender=self.__class__, user=user,
-                                       review_request_draft=draft)
+                                       review_request_draft=draft,
+                                       automated=automated)
 
         # Decrement the counts on everything. We'll increment the resulting
         # set during _update_counts() (called from ReviewRequest.save()).
@@ -1185,7 +1215,8 @@ class ReviewRequest(BaseReviewRequestDetails):
                                         send_notification=False,
                                         user=user,
                                         validate_fields=validate_fields,
-                                        timestamp=timestamp)
+                                        timestamp=timestamp,
+                                        automated=automated)
             except Exception:
                 # The draft failed to publish, for one reason or another.
                 # Check if we need to re-increment those counters we
@@ -1210,7 +1241,8 @@ class ReviewRequest(BaseReviewRequestDetails):
 
         review_request_published.send(sender=self.__class__, user=user,
                                       review_request=self, trivial=trivial,
-                                      changedesc=changes)
+                                      changedesc=changes,
+                                      automated=automated)
 
     def determine_user_for_changedesc(self, changedesc):
         """Determine the user associated with the change description.
diff --git a/reviewboard/reviews/models/review_request_draft.py b/reviewboard/reviews/models/review_request_draft.py
index a6bba1a0cf9a4a7d15a0737960489f3e4e76fa13..d6cded6eec70a7a82ae70997da16865002bd5d22 100644
--- a/reviewboard/reviews/models/review_request_draft.py
+++ b/reviewboard/reviews/models/review_request_draft.py
@@ -239,7 +239,8 @@ class ReviewRequestDraft(BaseReviewRequestDetails):
         return draft
 
     def publish(self, review_request=None, user=None, trivial=False,
-                send_notification=True, validate_fields=True, timestamp=None):
+                send_notification=True, validate_fields=True, timestamp=None,
+                automated=None):
         """Publish this draft.
 
         This is an internal method. Programmatic publishes should use
@@ -316,6 +317,10 @@ class ReviewRequestDraft(BaseReviewRequestDetails):
                 :py:class:`~reviewboard.changedescs.models.ChangeDescription`)
                 over the course of the method.
 
+            automated (bool, optional):
+                An optional field that indicates whether or not the review
+                request draft is published automatically.
+
         Returns:
             reviewboard.changedescs.models.ChangeDescription:
             The change description that results from this publish (if any).
@@ -379,6 +384,10 @@ class ReviewRequestDraft(BaseReviewRequestDetails):
             self.changedesc.user = user
             self.changedesc.timestamp = timestamp
             self.changedesc.public = True
+
+            if automated:
+                self.changedesc.extra_data['automated'] = True
+
             self.changedesc.save()
             review_request.changedescs.add(self.changedesc)
 
@@ -392,7 +401,8 @@ class ReviewRequestDraft(BaseReviewRequestDetails):
                                           user=user,
                                           review_request=review_request,
                                           trivial=trivial,
-                                          changedesc=self.changedesc)
+                                          changedesc=self.changedesc,
+                                          automated=automated)
 
         return self.changedesc
 
diff --git a/reviewboard/reviews/signals.py b/reviewboard/reviews/signals.py
index 069139462411d4e64de1a8452157fb811c44c6ab..e9f3b3129132fdccc8a324196ea8d61d3c6314c4 100644
--- a/reviewboard/reviews/signals.py
+++ b/reviewboard/reviews/signals.py
@@ -11,8 +11,12 @@ from django.dispatch import Signal
 #:
 #:     review_request_draft (reviewboard.reviews.models.ReviewRequestDraft):
 #:         The review request draft being published.
+#:
+#:     automated (bool):
+#:          Whether or not the review request is published automatically.
 review_request_publishing = Signal(providing_args=['user',
-                                                   'review_request_draft'])
+                                                   'review_request_draft',
+                                                   'automated'])
 
 
 #: Emitted when a review request is published.
@@ -29,8 +33,12 @@ review_request_publishing = Signal(providing_args=['user',
 #:
 #:     changedesc (reviewboard.changedescs.models.ChangeDescription):
 #:         The change description associated with the publish, if any.
+#:
+#:     automated (bool):
+#:          Whether or not the review request is published automatically.
 review_request_published = Signal(
-    providing_args=['user', 'review_request', 'trivial', 'changedesc'])
+    providing_args=['user', 'review_request', 'trivial', 'changedesc',
+                    'automated'])
 
 
 #: Emitted when a review request is about to be closed.
@@ -52,8 +60,12 @@ review_request_published = Signal(
 #:
 #:     rich_text (bool):
 #:         Whether or not the description is rich text (Markdown).
+#:
+#:     automated (bool):
+#:          Whether or not the review request is closed automatically.
 review_request_closing = Signal(providing_args=[
-    'user', 'review_request',  'close_type', 'description', 'rich_text'])
+    'user', 'review_request',  'close_type', 'description', 'rich_text',
+    'automated'])
 
 
 #: Emitted when a review request has been closed.
@@ -75,8 +87,12 @@ review_request_closing = Signal(providing_args=[
 #:
 #:     rich_text (bool):
 #:         Whether or not the description is rich text (Markdown).
+#:
+#:     automated (bool):
+#:          Whether or not the review request is closed automatically.
 review_request_closed = Signal(providing_args=['user', 'review_request',
-                                               'description', 'rich_text'])
+                                               'description', 'rich_text',
+                                               'automated'])
 
 
 #: Emitted when a review request is about to be reopened.
diff --git a/reviewboard/reviews/tests/test_entries.py b/reviewboard/reviews/tests/test_entries.py
index c43cf0b940e9a1f310975984a4b333ab4ece6732..48433f0cca1ff1d8417564b1f992c2d7cae5bcd7 100644
--- a/reviewboard/reviews/tests/test_entries.py
+++ b/reviewboard/reviews/tests/test_entries.py
@@ -1864,3 +1864,43 @@ class ChangeEntryTests(TestCase):
         self.assertFalse(entry.is_entry_new(
             last_visited=self.changedesc.timestamp + timedelta(days=1),
             user=user))
+
+    def test_avatar_with_automated(self):
+        """Testing ChangeEntry avatar rendering with automated=True"""
+        self.changedesc.extra_data['automated'] = True
+
+        self.data.query_data_pre_etag()
+        self.data.query_data_post_etag()
+
+        entry = ChangeEntry(data=self.data,
+                            changedesc=self.changedesc)
+
+        entry.template_name = 'reviews/entries/change.html'
+
+        html = entry.render_to_string(
+            self.request,
+            RequestContext(self.request, {
+                'last_visited': timezone.now(),
+            }))
+
+        self.assertNotEqual(html, '')
+        self.assertIn('class="avatar rb-logo"', html)
+
+    def test_avatar_not_automated(self):
+        """Testing ChangeEntry avatar rendering without automated=True"""
+        self.data.query_data_pre_etag()
+        self.data.query_data_post_etag()
+
+        entry = ChangeEntry(data=self.data,
+                            changedesc=self.changedesc)
+
+        entry.template_name = 'reviews/entries/change.html'
+
+        html = entry.render_to_string(
+            self.request,
+            RequestContext(self.request, {
+                'last_visited': timezone.now(),
+            }))
+
+        self.assertNotEqual(html, '')
+        self.assertIn('class="avatar djblets-o-avatar"', html)
diff --git a/reviewboard/reviews/tests/test_review_request.py b/reviewboard/reviews/tests/test_review_request.py
index c52d4acf7f75d222376a426fc808c24d7460d9dc..d48539604bf6f4e92805eaf51214ff962cf3f5e2 100644
--- a/reviewboard/reviews/tests/test_review_request.py
+++ b/reviewboard/reviews/tests/test_review_request.py
@@ -11,7 +11,11 @@ from reviewboard.reviews.errors import PublishError
 from reviewboard.reviews.models import (Comment, ReviewRequest,
                                         ReviewRequestDraft)
 from reviewboard.reviews.signals import (review_request_reopened,
-                                         review_request_reopening)
+                                         review_request_reopening,
+                                         review_request_closing,
+                                         review_request_closed,
+                                         review_request_publishing,
+                                         review_request_published)
 from reviewboard.scmtools.core import ChangeSet
 from reviewboard.testing import TestCase
 
@@ -65,6 +69,78 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
         self.assertIn('is_rich_text', close_info)
         self.assertTrue(close_info['is_rich_text'])
 
+    def test_close_with_automated(self):
+        """Testing ReviewRequest.close with automated=True when a review
+        request is closed automatically
+        """
+        review_request = self.create_review_request(publish=True)
+
+        self.spy_on(review_request_closing.send)
+        self.spy_on(review_request_closed.send)
+
+        review_request.close(close_type=ReviewRequest.SUBMITTED,
+                             description='test123', rich_text=True,
+                             automated=True)
+
+        changedesc = review_request.changedescs.get()
+
+        self.assertEqual(changedesc.text, 'test123')
+        self.assertTrue(changedesc.rich_text)
+        self.assertIsNotNone(changedesc.extra_data.get('automated'))
+        self.assertTrue(changedesc.extra_data['automated'])
+
+        self.assertSpyCalledWith(review_request_closing.send,
+                                 sender=ReviewRequest,
+                                 user=None,
+                                 review_request=review_request,
+                                 close_type=ReviewRequest.SUBMITTED,
+                                 description='test123',
+                                 rich_text=True,
+                                 automated=True)
+        self.assertSpyCalledWith(review_request_closed.send,
+                                 sender=ReviewRequest,
+                                 user=None,
+                                 review_request=review_request,
+                                 close_type=ReviewRequest.SUBMITTED,
+                                 description='test123',
+                                 rich_text=True,
+                                 automated=True)
+
+    def test_close_not_automated(self):
+        """Testing ReviewRequest.close without automated=True when a
+        review request is not closed automatically
+        """
+        review_request = self.create_review_request(publish=True)
+
+        self.spy_on(review_request_closing.send)
+        self.spy_on(review_request_closed.send)
+
+        review_request.close(close_type=ReviewRequest.SUBMITTED,
+                             description='test123', rich_text=True)
+
+        changedesc = review_request.changedescs.get()
+
+        self.assertEqual(changedesc.text, 'test123')
+        self.assertTrue(changedesc.rich_text)
+        self.assertIsNone(changedesc.extra_data.get('automated'))
+
+        self.assertSpyCalledWith(review_request_closing.send,
+                                 sender=ReviewRequest,
+                                 user=None,
+                                 review_request=review_request,
+                                 close_type=ReviewRequest.SUBMITTED,
+                                 description='test123',
+                                 rich_text=True,
+                                 automated=None)
+        self.assertSpyCalledWith(review_request_closed.send,
+                                 sender=ReviewRequest,
+                                 user=None,
+                                 review_request=review_request,
+                                 close_type=ReviewRequest.SUBMITTED,
+                                 description='test123',
+                                 rich_text=True,
+                                 automated=None)
+
     def test_get_close_info_timestamp_not_updated_by_reviews(self):
         """Testing ReviewRequest.get_close_info timestamp unnaffected by
         subsequent reviews on review requests.
@@ -303,6 +379,74 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
         with self.assertRaises(ChangeDescription.DoesNotExist):
             review_request.changedescs.filter(public=True).latest()
 
+    def test_publish_with_automated(self):
+        """Testing ReviewRequest.publish with automated=True when a review
+        request is updated and published automatically
+        """
+        doc = User.objects.get(username='doc')
+        review_request = self.create_review_request(publish=True,
+                                                    target_people=[doc])
+
+        draft = ReviewRequestDraft.create(review_request)
+        draft.summary = 'A new summary'
+        draft.save()
+
+        review_request = ReviewRequest.objects.get(
+            pk=review_request.pk)
+
+        self.spy_on(review_request_publishing.send)
+        self.spy_on(review_request_published.send)
+
+        review_request.publish(user=review_request.submitter,
+                               automated=True)
+
+        changedesc = review_request.changedescs.latest()
+
+        self.assertIsNotNone(changedesc)
+        self.assertEqual(changedesc.fields_changed['summary']['new'][0],
+                         draft.summary)
+        self.assertIsNotNone(changedesc.extra_data.get('automated'))
+        self.assertTrue(changedesc.extra_data['automated'])
+
+        self.assertSpyCalledWith(review_request_publishing.send,
+                                 automated=True)
+        self.assertSpyCalledWith(review_request_published.send,
+                                 changedesc=changedesc,
+                                 automated=True)
+
+    def test_publish_not_automated(self):
+        """Testing ReviewRequest.publish without automated=True when a review
+        request is not updated and published automatically
+        """
+        doc = User.objects.get(username='doc')
+        review_request = self.create_review_request(publish=True,
+                                                    target_people=[doc])
+
+        draft = ReviewRequestDraft.create(review_request)
+        draft.summary = 'A new summary'
+        draft.save()
+
+        review_request = ReviewRequest.objects.get(
+            pk=review_request.pk)
+
+        self.spy_on(review_request_publishing.send)
+        self.spy_on(review_request_published.send)
+
+        review_request.publish(user=review_request.submitter)
+
+        changedesc = review_request.changedescs.latest()
+
+        self.assertIsNotNone(changedesc)
+        self.assertEqual(changedesc.fields_changed['summary']['new'][0],
+                         draft.summary)
+        self.assertIsNone(changedesc.extra_data.get('automated'))
+
+        self.assertSpyCalledWith(review_request_publishing.send,
+                                 automated=None)
+        self.assertSpyCalledWith(review_request_published.send,
+                                 changedesc=changedesc,
+                                 automated=None)
+
     def test_submit_nonpublic(self):
         """Testing ReviewRequest.close with non-public requests to ensure state
         transitions to SUBMITTED from non-public review request is not allowed
diff --git a/reviewboard/reviews/tests/test_review_request_draft.py b/reviewboard/reviews/tests/test_review_request_draft.py
index 413f5562aa30b93555d394692b5cdcd90500e2c7..03e954a663dae364f6dec169d808d29545a6ac7d 100644
--- a/reviewboard/reviews/tests/test_review_request_draft.py
+++ b/reviewboard/reviews/tests/test_review_request_draft.py
@@ -762,6 +762,47 @@ class ReviewRequestDraftTests(TestCase):
             self.assertEqual(review_request.status,
                              ReviewRequest.PENDING_REVIEW)
 
+    def test_publish_with_automated(self):
+        """Testing ReviewRequestDraft.publish with automated=True when a
+        review request is updated and published automatically
+        """
+        draft = self._get_draft()
+        review_request = draft.review_request
+
+        draft.summary = 'New summary'
+        draft.description = 'New description'
+        draft.target_people.add(review_request.submitter)
+
+        changes = draft.publish(automated=True)
+
+        self.assertIsNotNone(changes)
+        self.assertEqual(changes.fields_changed['summary']['new'][0],
+                         draft.summary)
+        self.assertEqual(changes.fields_changed['description']['new'][0],
+                         draft.description)
+        self.assertIsNotNone(changes.extra_data.get('automated'))
+        self.assertTrue(changes.extra_data['automated'])
+
+    def test_publish_not_automated(self):
+        """Testing ReviewRequestDraft.publish without automated=True when a
+        review request is not updated and published automatically
+        """
+        draft = self._get_draft()
+        review_request = draft.review_request
+
+        draft.summary = 'New summary'
+        draft.description = 'New description'
+        draft.target_people.add(review_request.submitter)
+
+        changes = draft.publish()
+
+        self.assertIsNotNone(changes)
+        self.assertEqual(changes.fields_changed['summary']['new'][0],
+                         draft.summary)
+        self.assertEqual(changes.fields_changed['description']['new'][0],
+                         draft.description)
+        self.assertIsNone(changes.extra_data.get('automated'))
+
     def _get_draft(self):
         """Convenience function for getting a new draft to work with."""
         review_request = self.create_review_request(publish=True)
diff --git a/reviewboard/templates/notifications/review_request_email.txt b/reviewboard/templates/notifications/review_request_email.txt
index 6c6c858a28111353c3fe40250e8f2f8f41b803ac..bbde1196941611e6fe1d92f93152e895f83dacc3 100644
--- a/reviewboard/templates/notifications/review_request_email.txt
+++ b/reviewboard/templates/notifications/review_request_email.txt
@@ -96,8 +96,10 @@ Repository: {{review_request.repository.name}}
 {% endif %}
 {% endcondense %}
 
+{% if not automated %}
 Thanks,
 
 {{ review_request.submitter|user_displayname }}
+{% endif %}
 {% endcondense %}
 {% endautoescape %}
diff --git a/reviewboard/templates/reviews/entries/change.html b/reviewboard/templates/reviews/entries/change.html
index fcdf3df0477a58cdbcdfadc7cbf600fff415b5cc..bbc5e2da68af06be497c4e213796c670e1ff8132 100644
--- a/reviewboard/templates/reviews/entries/change.html
+++ b/reviewboard/templates/reviews/entries/change.html
@@ -1,5 +1,5 @@
 {% extends "reviews/entries/base.html" %}
-{% load i18n reviewtags %}
+{% load i18n reviewtags staticfiles %}
 
 
 {% block entry_classes %}changedesc has-avatar{% endblock %}
@@ -8,6 +8,13 @@
 {% block entry_title %}{% trans "Review request changed" %}{% endblock %}
 {% block entry_new_label %}{% trans "New review request update" %}{% endblock %}
 
+{% block entry_avatar %}
+{% if entry.changedesc.extra_data and entry.changedesc.extra_data.automated %}
+    <img id="rb-logo" src="{% static 'rb/images/logo.png' %}" srcset="{% static 'rb/images/logo.png' %} 1x, {% static 'rb/images/logo.png' %} 2x" alt="" width="48" height="48" class="avatar rb-logo"/>
+{% else %}
+{{  block.super }}
+{% endif %}
+{% endblock entry_avatar %}
 
 {% block entry_content %}
 {%  with changedesc=entry.changedesc %}
diff --git a/reviewboard/webapi/resources/review_request.py b/reviewboard/webapi/resources/review_request.py
index 84ae7c41693fc66a4e9d888b57c8da4b8959a7c3..08615f58a9ce1a7902408a5283c09b5fce67af4c 100644
--- a/reviewboard/webapi/resources/review_request.py
+++ b/reviewboard/webapi/resources/review_request.py
@@ -657,6 +657,11 @@ class ReviewRequestResource(MarkdownFieldsMixin, WebAPIResource):
                             DIFF_PARSE_ERROR)
     @webapi_request_fields(
         optional={
+            'automated': {
+                'type': BooleanFieldType,
+                'description': 'Whether or not the review request is created '
+                               'automatically.',
+            },
             'changenum': {
                 'type': IntFieldType,
                 'description': 'The optional change number to look up for the '
@@ -877,6 +882,11 @@ class ReviewRequestResource(MarkdownFieldsMixin, WebAPIResource):
                             REPO_INFO_ERROR)
     @webapi_request_fields(
         optional={
+            'automated': {
+                'type': BooleanFieldType,
+                'description': 'Whether or not the review request is closed '
+                               'automatically.',
+            },
             'status': {
                 'type': ChoiceFieldType,
                 'choices': ('discarded', 'pending', 'submitted'),
@@ -957,7 +967,7 @@ class ReviewRequestResource(MarkdownFieldsMixin, WebAPIResource):
     )
     def update(self, request, status=None, changenum=None,
                close_description=None, close_description_text_type=None,
-               description=None, text_type=None,
+               description=None, text_type=None, automated=None,
                extra_fields={}, *args, **kwargs):
         """Updates the status of the review request.
 
@@ -1017,7 +1027,8 @@ class ReviewRequestResource(MarkdownFieldsMixin, WebAPIResource):
                             self._close_type_map[status],
                             request.user,
                             close_description,
-                            rich_text=close_description_rich_text)
+                            rich_text=close_description_rich_text,
+                            automated=automated)
                     except CloseError as e:
                         return CLOSE_ERROR.with_message(str(e))
 
diff --git a/reviewboard/webapi/resources/review_request_draft.py b/reviewboard/webapi/resources/review_request_draft.py
index b108f2df46249bfa840232a2670d5a1f163353e6..f499615331187ffdfed1179f44aca5c1451f09e3 100644
--- a/reviewboard/webapi/resources/review_request_draft.py
+++ b/reviewboard/webapi/resources/review_request_draft.py
@@ -195,6 +195,11 @@ class ReviewRequestDraftResource(MarkdownFieldsMixin, WebAPIResource):
     ]
 
     CREATE_UPDATE_OPTIONAL_FIELDS = {
+        'automated': {
+            'type': BooleanFieldType,
+            'description': 'Whether or not the review request is updated '
+                           'automatically.',
+        },
         'branch': {
             'type': StringFieldType,
             'description': 'The new branch name.',
@@ -431,6 +436,7 @@ class ReviewRequestDraftResource(MarkdownFieldsMixin, WebAPIResource):
                update_from_commit_id=False,
                trivial=None,
                publish_as_owner=False,
+               automated=None,
                extra_fields={},
                *args,
                **kwargs):
@@ -736,7 +742,8 @@ class ReviewRequestDraftResource(MarkdownFieldsMixin, WebAPIResource):
                 publish_user = request.user
 
             try:
-                review_request.publish(user=publish_user, trivial=trivial)
+                review_request.publish(user=publish_user, trivial=trivial,
+                                       automated=automated)
             except NotModifiedError:
                 return NOTHING_TO_PUBLISH
             except PublishError as e:
diff --git a/reviewboard/webapi/tests/test_review_request.py b/reviewboard/webapi/tests/test_review_request.py
index cc57c55b0fbb701515b4d19f1b56bd8ebf4bbeda..0f6777cc8e05d90a63b13aee80eb00ac6f21c8aa 100644
--- a/reviewboard/webapi/tests/test_review_request.py
+++ b/reviewboard/webapi/tests/test_review_request.py
@@ -2128,6 +2128,35 @@ class ResourceItemTests(ExtraDataItemMixin, BaseWebAPITestCase,
 
         c = r.changedescs.latest('timestamp')
         self.assertEqual(c.text, 'comment')
+        self.assertIsNone(c.extra_data.get('automated'))
+
+        fc_status = c.fields_changed['status']
+        self.assertEqual(fc_status['old'][0], 'P')
+        self.assertEqual(fc_status['new'][0], 'D')
+
+    def test_put_status_discarded_with_automated(self):
+        """Testing the PUT review-requests/<id>/?status=discarded API
+        with automated=True
+        """
+        r = self.create_review_request(submitter=self.user, publish=True)
+
+        rsp = self.api_put(
+            get_review_request_item_url(r.display_id),
+            {
+                'status': 'discarded',
+                'close_description': 'comment',
+                'automated': True,
+            },
+            expected_mimetype=review_request_item_mimetype)
+
+        self.assertEqual(rsp['stat'], 'ok')
+
+        r = ReviewRequest.objects.get(pk=r.id)
+        self.assertEqual(r.status, 'D')
+
+        c = r.changedescs.latest('timestamp')
+        self.assertEqual(c.text, 'comment')
+        self.assertTrue(c.extra_data.get('automated'))
 
         fc_status = c.fields_changed['status']
         self.assertEqual(fc_status['old'][0], 'P')
@@ -2183,6 +2212,35 @@ class ResourceItemTests(ExtraDataItemMixin, BaseWebAPITestCase,
 
         c = r.changedescs.latest('timestamp')
         self.assertEqual(c.text, 'comment')
+        self.assertIsNone(c.extra_data.get('automated'))
+
+        fc_status = c.fields_changed['status']
+        self.assertEqual(fc_status['old'][0], 'P')
+        self.assertEqual(fc_status['new'][0], 'S')
+
+    def test_put_status_submitted_with_automated(self):
+        """Testing the PUT review-requests/<id>/?status=submitted API
+        with automated=True
+        """
+        r = self.create_review_request(submitter=self.user, publish=True)
+
+        rsp = self.api_put(
+            get_review_request_item_url(r.display_id),
+            {
+                'status': 'submitted',
+                'close_description': 'comment',
+                'automated': True,
+            },
+            expected_mimetype=review_request_item_mimetype)
+
+        self.assertEqual(rsp['stat'], 'ok')
+
+        r = ReviewRequest.objects.get(pk=r.id)
+        self.assertEqual(r.status, 'S')
+
+        c = r.changedescs.latest('timestamp')
+        self.assertEqual(c.text, 'comment')
+        self.assertTrue(c.extra_data.get('automated'))
 
         fc_status = c.fields_changed['status']
         self.assertEqual(fc_status['old'][0], 'P')
diff --git a/reviewboard/webapi/tests/test_review_request_draft.py b/reviewboard/webapi/tests/test_review_request_draft.py
index add723b6ea50d6a073ec8a95652b50f68479bd72..677f7d5a3db6e030ec5c605b0c3597f9fe6cdf7c 100644
--- a/reviewboard/webapi/tests/test_review_request_draft.py
+++ b/reviewboard/webapi/tests/test_review_request_draft.py
@@ -442,6 +442,77 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
             sender=ReviewRequest,
             user=user))
 
+    def test_post_publish_with_automated(self):
+        """Testing the POST review-requests/<id>/draft/?public=1 API with
+        automated=True
+        """
+        user = User.objects.get(username='doc')
+        review_request = self.create_review_request(submitter=self.user,
+                                                    publish=True,
+                                                    target_people=[user])
+
+        self.spy_on(review_request_publishing.send)
+        self.spy_on(review_request_published.send)
+
+        rsp = self.api_post(
+            get_review_request_draft_url(review_request),
+            {
+                'summary': 'New summary',
+                'public': True,
+                'automated': True,
+            },
+            expected_mimetype=review_request_draft_item_mimetype)
+
+        self.assertEqual(rsp['stat'], 'ok')
+
+        review_request = ReviewRequest.objects.get(pk=review_request.id)
+        self.assertEqual(review_request.summary, "New summary")
+        self.assertTrue(review_request.public)
+
+        changedesc = review_request.changedescs.latest()
+        self.assertTrue(changedesc.extra_data.get('automated'))
+
+        self.assertSpyCalledWith(review_request_publishing.send,
+                                 automated=True)
+        self.assertSpyCalledWith(review_request_published.send,
+                                 changedesc=changedesc,
+                                 automated=True)
+
+    def test_post_publish_not_automated(self):
+        """Testing the POST review-requests/<id>/draft/?public=1 API without
+        automated=True
+        """
+        user = User.objects.get(username='doc')
+        review_request = self.create_review_request(submitter=self.user,
+                                                    publish=True,
+                                                    target_people=[user])
+
+        self.spy_on(review_request_publishing.send)
+        self.spy_on(review_request_published.send)
+
+        rsp = self.api_post(
+            get_review_request_draft_url(review_request),
+            {
+                'summary': 'New summary',
+                'public': True,
+            },
+            expected_mimetype=review_request_draft_item_mimetype)
+
+        self.assertEqual(rsp['stat'], 'ok')
+
+        review_request = ReviewRequest.objects.get(pk=review_request.id)
+        self.assertEqual(review_request.summary, "New summary")
+        self.assertTrue(review_request.public)
+
+        changedesc = review_request.changedescs.latest()
+        self.assertIsNone(changedesc.extra_data.get('automated'))
+
+        self.assertSpyCalledWith(review_request_publishing.send,
+                                 automated=None)
+        self.assertSpyCalledWith(review_request_published.send,
+                                 changedesc=changedesc,
+                                 automated=None)
+
     #
     # HTTP PUT tests
     #
@@ -1432,12 +1503,74 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
         self.assertEqual(review_request.branch, "My Branch")
         self.assertTrue(review_request.public)
 
+        changedesc = review_request.changedescs.latest()
+        self.assertIsNone(changedesc.extra_data.get('automated'))
+
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(
+            mail.outbox[0].subject,
+            "Re: Review Request %s: My Summary" % review_request.pk)
+        self.assertValidRecipients(["doc", "grumpy"])
+
+    def test_put_publish_with_automated(self):
+        """Testing the PUT review-requests/<id>/draft/?public=1 API with
+        automated=True
+        """
+        # We need to send e-mail out for both the initial review request
+        # publish and the draft publish in order for the latter to have a
+        # "Re:" in the subject.
+        with self.siteconfig_settings({'mail_send_review_mail': True},
+                                      reload_settings=False):
+            review_request = self.create_review_request(submitter=self.user,
+                                                        publish=True)
+
+            draft = ReviewRequestDraft.create(review_request)
+            draft.summary = 'My Summary'
+            draft.description = 'My Description'
+            draft.testing_done = 'My Testing Done'
+            draft.branch = 'My Branch'
+            draft.target_people.add(User.objects.get(username='doc'))
+            draft.save()
+
+            self.spy_on(review_request_publishing.send)
+            self.spy_on(review_request_published.send)
+
+            # Since we're only testing for the draft's publish e-mail,
+            # clear the outbox.
+            mail.outbox = []
+
+            rsp = self.api_put(
+                get_review_request_draft_url(review_request),
+                {
+                    'public': True,
+                    'automated': True,
+                },
+                expected_mimetype=review_request_draft_item_mimetype)
+
+        self.assertEqual(rsp['stat'], 'ok')
+
+        review_request = ReviewRequest.objects.get(pk=review_request.id)
+        self.assertEqual(review_request.summary, "My Summary")
+        self.assertEqual(review_request.description, "My Description")
+        self.assertEqual(review_request.testing_done, "My Testing Done")
+        self.assertEqual(review_request.branch, "My Branch")
+        self.assertTrue(review_request.public)
+
+        changedesc = review_request.changedescs.latest()
+        self.assertTrue(changedesc.extra_data.get('automated'))
+
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(
             mail.outbox[0].subject,
             "Re: Review Request %s: My Summary" % review_request.pk)
         self.assertValidRecipients(["doc", "grumpy"])
 
+        self.assertSpyCalledWith(review_request_publishing.send,
+                                 automated=True)
+        self.assertSpyCalledWith(review_request_published.send,
+                                 changedesc=changedesc,
+                                 automated=True)
+
     def test_put_publish_with_new_submitter(self):
         """Testing the PUT review-requests/<id>/draft/?public=1 API
         with new submitter
