diff --git a/reviewboard/hostingsvcs/hook_utils.py b/reviewboard/hostingsvcs/hook_utils.py
index fcf6764c9395e4f78f0ec676e5d1183842c0d843..5a27c044c2eacdb8c5f4aa13a8320ef4e2ba099c 100644
--- a/reviewboard/hostingsvcs/hook_utils.py
+++ b/reviewboard/hostingsvcs/hook_utils.py
@@ -75,7 +75,7 @@ def get_review_request_id(commit_message, server_url, commit_id=None,
 
 
 def close_review_request(review_request, review_request_id, description,
-                         automated=None):
+                         automated=None, automated_service_info={}):
     """Close the specified review request as submitted.
 
     This will close the specific review request that is published.
@@ -93,6 +93,10 @@ def close_review_request(review_request, review_request_id, description,
         automated (boolean, optional):
             An optional field that indicates whether or not the review request
             is closed automatically.
+
+        automated_service_info (dict, optional):
+            An optional data that describes the name of the hosting service
+            that closed the review request automatically.
     """
     if review_request.status == ReviewRequest.SUBMITTED:
         logging.warning('Review request #%s is already submitted.',
@@ -106,7 +110,8 @@ def close_review_request(review_request, review_request_id, description,
                                validate_fields=False)
 
     review_request.close(ReviewRequest.SUBMITTED, description=description,
-                         automated=automated)
+                         automated=automated,
+                         automated_service_info=automated_service_info)
     logging.debug('Review request #%s is set to %s.',
                   review_request_id, review_request.status)
 
@@ -174,4 +179,5 @@ def close_all_review_requests(review_request_id_to_commits, local_site_name,
             review_request_id,
             ('Pushed to ' +
              ', '.join(review_request_id_to_commits[review_request_id])),
-            automated=True)
+            automated=True,
+            automated_service_info={'service_name': hosting_service_id})
diff --git a/reviewboard/hostingsvcs/tests/test_bitbucket.py b/reviewboard/hostingsvcs/tests/test_bitbucket.py
index 24b871338b7eb7915396602abdcfac8239702f88..ddcc8c938ab2232864f23c31a41dda7e9df7ba13 100644
--- a/reviewboard/hostingsvcs/tests/test_bitbucket.py
+++ b/reviewboard/hostingsvcs/tests/test_bitbucket.py
@@ -1356,6 +1356,8 @@ class CloseSubmittedHookTests(BitbucketTestCase):
 
         self.assertEqual(changedesc.text, 'Pushed to master (1c44b46)')
         self.assertTrue(changedesc.extra_data.get('automated'))
+        self.assertEqual(changedesc.extra_data.get('automated_service_info'),
+                         {'service_name': 'bitbucket'})
 
     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 e5f6829c076f247e6a2ca2178f683006448056af..1cf5f20cb3484dcbc174a72c08f4de2694d3261a 100644
--- a/reviewboard/hostingsvcs/tests/test_github.py
+++ b/reviewboard/hostingsvcs/tests/test_github.py
@@ -1535,6 +1535,8 @@ class CloseSubmittedHookTests(GitHubTestCase):
 
         self.assertEqual(changedesc.text, 'Pushed to master (1c44b46)')
         self.assertTrue(changedesc.extra_data.get('automated'))
+        self.assertEqual(changedesc.extra_data.get('automated_service_info'),
+                         {'service_name': 'github'})
 
     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 dfae8f8187249b240ff1b33f93d3c25dc9abe757..141bbb4693fec9796440a754fafdda584fc9e3dc 100644
--- a/reviewboard/hostingsvcs/tests/test_rbgateway.py
+++ b/reviewboard/hostingsvcs/tests/test_rbgateway.py
@@ -703,6 +703,8 @@ class CloseSubmittedHookTests(ReviewBoardGatewayTestCase):
 
         self.assertEqual(changedesc.text, expected_close_msg)
         self.assertTrue(changedesc.extra_data.get('automated'))
+        self.assertEqual(changedesc.extra_data.get('automated_service_info'),
+                         {'service_name': 'rbgateway'})
 
     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 b35405afb20b0442c8848c625e4addfdbd7e41a5..63539d763dd2bbffbb2041847f5f4e42661adfcf 100644
--- a/reviewboard/notifications/email/message.py
+++ b/reviewboard/notifications/email/message.py
@@ -496,6 +496,8 @@ def prepare_review_request_mail(user, review_request, changedesc=None,
 
         extra_context.update({
             'automated': changedesc.extra_data.get('automated', False),
+            'automated_service_info':
+                changedesc.extra_data.get('automated_service_info', None)
         })
 
         if (changed_field_names and
diff --git a/reviewboard/notifications/tests/test_email_sending.py b/reviewboard/notifications/tests/test_email_sending.py
index de0bd7a40b718bdcc7f08140f11ac4e059ee107a..cd4f7b8c20edb1609b1fd21dffb70e851bcdf5fc 100644
--- a/reviewboard/notifications/tests/test_email_sending.py
+++ b/reviewboard/notifications/tests/test_email_sending.py
@@ -713,7 +713,8 @@ class ReviewRequestEmailTests(ReviewRequestEmailTestsMixin, DmarcDnsTestsMixin,
 
     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
+        automatically (without automated=True and automated_service_info)
+        and e-mail setting is True
         """
         with self.siteconfig_settings({'mail_send_review_close_mail': True},
                                       reload_settings=False):
@@ -756,7 +757,8 @@ class ReviewRequestEmailTests(ReviewRequestEmailTestsMixin, DmarcDnsTestsMixin,
 
     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
+        automatically (with automated=True and automated_service_info)
+        and e-mail setting is True
         """
         with self.siteconfig_settings({'mail_send_review_close_mail': True},
                                       reload_settings=False):
@@ -770,9 +772,15 @@ class ReviewRequestEmailTests(ReviewRequestEmailTestsMixin, DmarcDnsTestsMixin,
             self.spy_on(prepare_review_request_mail)
             self.spy_on(prepare_base_review_request_mail)
 
+            automated_service_info = {
+                'service_name': 'automated_service',
+                'id': 1,
+            }
+
             review_request.close(ReviewRequest.SUBMITTED,
                                  description='Test with automated',
-                                 automated=True)
+                                 automated=True,
+                                 automated_service_info=automated_service_info)
 
             self.assertEqual(len(mail.outbox), 1)
             self.assertEqual(mail.outbox[0].extra_headers['From'],
@@ -972,7 +980,7 @@ class ReviewRequestEmailTests(ReviewRequestEmailTestsMixin, DmarcDnsTestsMixin,
 
     def test_update_review_request_email_with_automated(self):
         """Testing sending an e-mail when a review request is updated
-        automatically (automated=True)
+        automatically (with automated=True and automated_service_info)
         """
         group = Group.objects.create(name='devgroup',
                                      mailing_list='devgroup@example.com')
@@ -993,7 +1001,14 @@ class ReviewRequestEmailTests(ReviewRequestEmailTestsMixin, DmarcDnsTestsMixin,
         # clear the outbox.
         mail.outbox = []
 
-        review_request.publish(review_request.submitter, automated=True)
+        automated_service_info = {
+            'service_name': 'automated_service',
+            'id': 1,
+        }
+
+        review_request.publish(review_request.submitter,
+                               automated=True,
+                               automated_service_info=automated_service_info)
 
         changedesc = review_request.changedescs.latest()
 
@@ -1026,7 +1041,7 @@ class ReviewRequestEmailTests(ReviewRequestEmailTestsMixin, DmarcDnsTestsMixin,
 
     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)
+        automatically (without automated=True and automated_service_info)
         """
         group = Group.objects.create(name='devgroup',
                                      mailing_list='devgroup@example.com')
diff --git a/reviewboard/reviews/models/review_request.py b/reviewboard/reviews/models/review_request.py
index f21f0f1ab5a7f90c35a78821d2c34ef909db8b08..a826c6e1237a82545d5edfe00f86f7c631c2511c 100644
--- a/reviewboard/reviews/models/review_request.py
+++ b/reviewboard/reviews/models/review_request.py
@@ -948,7 +948,8 @@ class ReviewRequest(BaseReviewRequestDetails):
         )
 
     def close(self, close_type=None, user=None, description=None,
-              rich_text=False, automated=None, **kwargs):
+              rich_text=False, automated=None, automated_service_info={},
+              **kwargs):
         """Closes the review request.
 
         Args:
@@ -970,6 +971,10 @@ class ReviewRequest(BaseReviewRequestDetails):
                 An optional field that indicates whether or not the review
                 request is closed automatically.
 
+            automated_service_info (dict, optional):
+                An optional data that describes the details of the service that
+                closed the review request automatically.
+
         Raises:
             ValueError:
                 The provided close type is not a valid value.
@@ -1017,7 +1022,8 @@ class ReviewRequest(BaseReviewRequestDetails):
             close_type=close_type,
             description=description,
             rich_text=rich_text,
-            automated=automated)
+            automated=automated,
+            automated_service_info=automated_service_info)
 
         draft = get_object_or_none(self.draft)
 
@@ -1036,6 +1042,10 @@ class ReviewRequest(BaseReviewRequestDetails):
             if automated:
                 changedesc.extra_data['automated'] = True
 
+            if automated_service_info:
+                changedesc.extra_data['automated_service_info'] = \
+                    automated_service_info
+
             status_field = get_review_request_field('status')(self)
             status_field.record_change_entry(changedesc, self.status,
                                              close_type)
@@ -1059,7 +1069,8 @@ class ReviewRequest(BaseReviewRequestDetails):
                 close_type=close_type,
                 description=description,
                 rich_text=rich_text,
-                automated=automated)
+                automated=automated,
+                automated_service_info=automated_service_info)
         else:
             # Update submission description.
             changedesc = self.changedescs.filter(public=True).latest()
@@ -1119,7 +1130,7 @@ class ReviewRequest(BaseReviewRequestDetails):
                                      old_public=old_public)
 
     def publish(self, user, trivial=False, validate_fields=True,
-                automated=None):
+                automated=None, automated_service_info={}):
         """Publishes the current draft attached to this review request.
 
         The review request will be mark as public, and signals will be
@@ -1143,6 +1154,10 @@ class ReviewRequest(BaseReviewRequestDetails):
             automated (bool, optional):
                 An optional field that indicates whether or not the review
                 request is published automatically.
+
+            automated_service_info (dict, optional):
+                An optional data that describes the details of the service that
+                published the review request automatically.
         """
         if not self.is_mutable_by(user):
             raise PermissionError
@@ -1186,7 +1201,9 @@ class ReviewRequest(BaseReviewRequestDetails):
 
         review_request_publishing.send(sender=self.__class__, user=user,
                                        review_request_draft=draft,
-                                       automated=automated)
+                                       automated=automated,
+                                       automated_service_info=(
+                                           automated_service_info))
 
         # Decrement the counts on everything. We'll increment the resulting
         # set during _update_counts() (called from ReviewRequest.save()).
@@ -1216,7 +1233,9 @@ class ReviewRequest(BaseReviewRequestDetails):
                                         user=user,
                                         validate_fields=validate_fields,
                                         timestamp=timestamp,
-                                        automated=automated)
+                                        automated=automated,
+                                        automated_service_info=(
+                                            automated_service_info))
             except Exception:
                 # The draft failed to publish, for one reason or another.
                 # Check if we need to re-increment those counters we
@@ -1242,7 +1261,9 @@ class ReviewRequest(BaseReviewRequestDetails):
         review_request_published.send(sender=self.__class__, user=user,
                                       review_request=self, trivial=trivial,
                                       changedesc=changes,
-                                      automated=automated)
+                                      automated=automated,
+                                      automated_service_info=(
+                                          automated_service_info))
 
     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 d6cded6eec70a7a82ae70997da16865002bd5d22..fdcbdd9a3978723e8857355552793c9d66f8e101 100644
--- a/reviewboard/reviews/models/review_request_draft.py
+++ b/reviewboard/reviews/models/review_request_draft.py
@@ -240,7 +240,7 @@ class ReviewRequestDraft(BaseReviewRequestDetails):
 
     def publish(self, review_request=None, user=None, trivial=False,
                 send_notification=True, validate_fields=True, timestamp=None,
-                automated=None):
+                automated=None, automated_service_info={}):
         """Publish this draft.
 
         This is an internal method. Programmatic publishes should use
@@ -321,6 +321,10 @@ class ReviewRequestDraft(BaseReviewRequestDetails):
                 An optional field that indicates whether or not the review
                 request draft is published automatically.
 
+            automated_service_info (dict, optional):
+                An optional data that describes the details of the service that
+                published the review request automatically.
+
         Returns:
             reviewboard.changedescs.models.ChangeDescription:
             The change description that results from this publish (if any).
@@ -388,6 +392,10 @@ class ReviewRequestDraft(BaseReviewRequestDetails):
             if automated:
                 self.changedesc.extra_data['automated'] = True
 
+            if automated_service_info:
+                self.changedesc.extra_data['automated_service_info'] = \
+                    automated_service_info
+
             self.changedesc.save()
             review_request.changedescs.add(self.changedesc)
 
@@ -402,7 +410,9 @@ class ReviewRequestDraft(BaseReviewRequestDetails):
                                           review_request=review_request,
                                           trivial=trivial,
                                           changedesc=self.changedesc,
-                                          automated=automated)
+                                          automated=automated,
+                                          automated_service_info=(
+                                              automated_service_info))
 
         return self.changedesc
 
diff --git a/reviewboard/reviews/signals.py b/reviewboard/reviews/signals.py
index e9f3b3129132fdccc8a324196ea8d61d3c6314c4..0c335fb019749fc2c1e318b33d1c9b0c22fc2c66 100644
--- a/reviewboard/reviews/signals.py
+++ b/reviewboard/reviews/signals.py
@@ -14,9 +14,14 @@ from django.dispatch import Signal
 #:
 #:     automated (bool):
 #:          Whether or not the review request is published automatically.
+#:
+#:     automated_service_info (dict):
+#:           Data that describes the details of the service that published the
+#:           review request automatically.
 review_request_publishing = Signal(providing_args=['user',
                                                    'review_request_draft',
-                                                   'automated'])
+                                                   'automated',
+                                                   'automated_service_info'])
 
 
 #: Emitted when a review request is published.
@@ -36,9 +41,13 @@ review_request_publishing = Signal(providing_args=['user',
 #:
 #:     automated (bool):
 #:          Whether or not the review request is published automatically.
+#:
+#:     automated_service_info (dict):
+#:           Data that describes the details of the service that published the
+#:           review request automatically.
 review_request_published = Signal(
     providing_args=['user', 'review_request', 'trivial', 'changedesc',
-                    'automated'])
+                    'automated', 'automated_service_info'])
 
 
 #: Emitted when a review request is about to be closed.
@@ -63,9 +72,13 @@ review_request_published = Signal(
 #:
 #:     automated (bool):
 #:          Whether or not the review request is closed automatically.
+#:
+#:     automated_service_info (dict):
+#:           Data that describes the details of the service that closed the
+#:           review request automatically.
 review_request_closing = Signal(providing_args=[
     'user', 'review_request',  'close_type', 'description', 'rich_text',
-    'automated'])
+    'automated', 'automated_service_info'])
 
 
 #: Emitted when a review request has been closed.
@@ -90,9 +103,14 @@ review_request_closing = Signal(providing_args=[
 #:
 #:     automated (bool):
 #:          Whether or not the review request is closed automatically.
+#:
+#:     automated_service_info (dict):
+#:           Data that describes the details of the service that closed the
+#:           review request automatically.
 review_request_closed = Signal(providing_args=['user', 'review_request',
                                                'description', 'rich_text',
-                                               'automated'])
+                                               'automated',
+                                               'automated_service_info'])
 
 
 #: Emitted when a review request is about to be reopened.
diff --git a/reviewboard/reviews/tests/test_review_request.py b/reviewboard/reviews/tests/test_review_request.py
index d48539604bf6f4e92805eaf51214ff962cf3f5e2..60988cfe2b0dbbc32bc93f2b58aa77fcec6895ec 100644
--- a/reviewboard/reviews/tests/test_review_request.py
+++ b/reviewboard/reviews/tests/test_review_request.py
@@ -70,17 +70,23 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
         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
+        """Testing ReviewRequest.close with automated=True and
+        automated_service_info 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)
 
+        automated_service_info = {
+            'service_name': 'automated_service',
+            'id': 1,
+        }
+
         review_request.close(close_type=ReviewRequest.SUBMITTED,
                              description='test123', rich_text=True,
-                             automated=True)
+                             automated=True,
+                             automated_service_info=automated_service_info)
 
         changedesc = review_request.changedescs.get()
 
@@ -88,6 +94,8 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
         self.assertTrue(changedesc.rich_text)
         self.assertIsNotNone(changedesc.extra_data.get('automated'))
         self.assertTrue(changedesc.extra_data['automated'])
+        self.assertEqual(changedesc.extra_data.get('automated_service_info'),
+                         automated_service_info)
 
         self.assertSpyCalledWith(review_request_closing.send,
                                  sender=ReviewRequest,
@@ -96,7 +104,8 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
                                  close_type=ReviewRequest.SUBMITTED,
                                  description='test123',
                                  rich_text=True,
-                                 automated=True)
+                                 automated=True,
+                                 automated_service_info=automated_service_info)
         self.assertSpyCalledWith(review_request_closed.send,
                                  sender=ReviewRequest,
                                  user=None,
@@ -104,11 +113,13 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
                                  close_type=ReviewRequest.SUBMITTED,
                                  description='test123',
                                  rich_text=True,
-                                 automated=True)
+                                 automated=True,
+                                 automated_service_info=automated_service_info)
 
     def test_close_not_automated(self):
-        """Testing ReviewRequest.close without automated=True when a
-        review request is not closed automatically
+        """Testing ReviewRequest.close without automated=True and
+        automated_service_info when a review request is not closed
+        automatically
         """
         review_request = self.create_review_request(publish=True)
 
@@ -123,6 +134,7 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
         self.assertEqual(changedesc.text, 'test123')
         self.assertTrue(changedesc.rich_text)
         self.assertIsNone(changedesc.extra_data.get('automated'))
+        self.assertIsNone(changedesc.extra_data.get('automated_service_info'))
 
         self.assertSpyCalledWith(review_request_closing.send,
                                  sender=ReviewRequest,
@@ -131,7 +143,8 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
                                  close_type=ReviewRequest.SUBMITTED,
                                  description='test123',
                                  rich_text=True,
-                                 automated=None)
+                                 automated=None,
+                                 automated_service_info={})
         self.assertSpyCalledWith(review_request_closed.send,
                                  sender=ReviewRequest,
                                  user=None,
@@ -139,7 +152,8 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
                                  close_type=ReviewRequest.SUBMITTED,
                                  description='test123',
                                  rich_text=True,
-                                 automated=None)
+                                 automated=None,
+                                 automated_service_info={})
 
     def test_get_close_info_timestamp_not_updated_by_reviews(self):
         """Testing ReviewRequest.get_close_info timestamp unnaffected by
@@ -380,8 +394,9 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
             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
+        """Testing ReviewRequest.publish with automated=True and
+        automated_service_info when a review request is updated and published
+        automatically
         """
         doc = User.objects.get(username='doc')
         review_request = self.create_review_request(publish=True,
@@ -397,8 +412,14 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
         self.spy_on(review_request_publishing.send)
         self.spy_on(review_request_published.send)
 
+        automated_service_info = {
+            'service_name': 'automated_service',
+            'id': 1,
+        }
+
         review_request.publish(user=review_request.submitter,
-                               automated=True)
+                               automated=True,
+                               automated_service_info=automated_service_info)
 
         changedesc = review_request.changedescs.latest()
 
@@ -407,16 +428,21 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
                          draft.summary)
         self.assertIsNotNone(changedesc.extra_data.get('automated'))
         self.assertTrue(changedesc.extra_data['automated'])
+        self.assertEqual(changedesc.extra_data.get('automated_service_info'),
+                         automated_service_info)
 
         self.assertSpyCalledWith(review_request_publishing.send,
-                                 automated=True)
+                                 automated=True,
+                                 automated_service_info=automated_service_info)
         self.assertSpyCalledWith(review_request_published.send,
                                  changedesc=changedesc,
-                                 automated=True)
+                                 automated=True,
+                                 automated_service_info=automated_service_info)
 
     def test_publish_not_automated(self):
-        """Testing ReviewRequest.publish without automated=True when a review
-        request is not updated and published automatically
+        """Testing ReviewRequest.publish without automated=True and
+        automated_service_info when a review request is not updated and
+        published automatically
         """
         doc = User.objects.get(username='doc')
         review_request = self.create_review_request(publish=True,
@@ -440,12 +466,15 @@ class ReviewRequestTests(kgb.SpyAgency, TestCase):
         self.assertEqual(changedesc.fields_changed['summary']['new'][0],
                          draft.summary)
         self.assertIsNone(changedesc.extra_data.get('automated'))
+        self.assertIsNone(changedesc.extra_data.get('automated_service_info'))
 
         self.assertSpyCalledWith(review_request_publishing.send,
-                                 automated=None)
+                                 automated=None,
+                                 automated_service_info={})
         self.assertSpyCalledWith(review_request_published.send,
                                  changedesc=changedesc,
-                                 automated=None)
+                                 automated=None,
+                                 automated_service_info={})
 
     def test_submit_nonpublic(self):
         """Testing ReviewRequest.close with non-public requests to ensure state
diff --git a/reviewboard/reviews/tests/test_review_request_draft.py b/reviewboard/reviews/tests/test_review_request_draft.py
index 03e954a663dae364f6dec169d808d29545a6ac7d..dd636c7ae687c71cc5287e23a782c7570ed6ab46 100644
--- a/reviewboard/reviews/tests/test_review_request_draft.py
+++ b/reviewboard/reviews/tests/test_review_request_draft.py
@@ -763,8 +763,9 @@ class ReviewRequestDraftTests(TestCase):
                              ReviewRequest.PENDING_REVIEW)
 
     def test_publish_with_automated(self):
-        """Testing ReviewRequestDraft.publish with automated=True when a
-        review request is updated and published automatically
+        """Testing ReviewRequestDraft.publish with automated=True and
+        automated_service_info when a review request is updated and published
+        automatically
         """
         draft = self._get_draft()
         review_request = draft.review_request
@@ -773,7 +774,13 @@ class ReviewRequestDraftTests(TestCase):
         draft.description = 'New description'
         draft.target_people.add(review_request.submitter)
 
-        changes = draft.publish(automated=True)
+        automated_service_info = {
+            'service_name': 'automated_service',
+            'id': 1,
+        }
+
+        changes = draft.publish(automated=True,
+                                automated_service_info=automated_service_info)
 
         self.assertIsNotNone(changes)
         self.assertEqual(changes.fields_changed['summary']['new'][0],
@@ -782,10 +789,13 @@ class ReviewRequestDraftTests(TestCase):
                          draft.description)
         self.assertIsNotNone(changes.extra_data.get('automated'))
         self.assertTrue(changes.extra_data['automated'])
+        self.assertEqual(changes.extra_data.get('automated_service_info'),
+                         automated_service_info)
 
     def test_publish_not_automated(self):
-        """Testing ReviewRequestDraft.publish without automated=True when a
-        review request is not updated and published automatically
+        """Testing ReviewRequestDraft.publish without automated=True and
+        automated_service_info when a review request is not updated and
+        published automatically
         """
         draft = self._get_draft()
         review_request = draft.review_request
@@ -802,6 +812,7 @@ class ReviewRequestDraftTests(TestCase):
         self.assertEqual(changes.fields_changed['description']['new'][0],
                          draft.description)
         self.assertIsNone(changes.extra_data.get('automated'))
+        self.assertIsNone(changes.extra_data.get('automated_service_info'))
 
     def _get_draft(self):
         """Convenience function for getting a new draft to work with."""
diff --git a/reviewboard/webapi/resources/review_request.py b/reviewboard/webapi/resources/review_request.py
index 08615f58a9ce1a7902408a5283c09b5fce67af4c..9d7cbccdc275a5c88e415076f4bd9c7a8f75b631 100644
--- a/reviewboard/webapi/resources/review_request.py
+++ b/reviewboard/webapi/resources/review_request.py
@@ -662,6 +662,12 @@ class ReviewRequestResource(MarkdownFieldsMixin, WebAPIResource):
                 'description': 'Whether or not the review request is created '
                                'automatically.',
             },
+            'automated_service_info': {
+                'type': DictFieldType,
+                'description': 'Data that describes the details of the '
+                               'service that created the review request '
+                               'automatically.',
+            },
             'changenum': {
                 'type': IntFieldType,
                 'description': 'The optional change number to look up for the '
@@ -887,6 +893,12 @@ class ReviewRequestResource(MarkdownFieldsMixin, WebAPIResource):
                 'description': 'Whether or not the review request is closed '
                                'automatically.',
             },
+            'automated_service_info': {
+                'type': DictFieldType,
+                'description': 'Data that describes the details of the '
+                               'service that closed the review request '
+                               'automatically.',
+            },
             'status': {
                 'type': ChoiceFieldType,
                 'choices': ('discarded', 'pending', 'submitted'),
@@ -968,7 +980,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, automated=None,
-               extra_fields={}, *args, **kwargs):
+               automated_service_info={}, extra_fields={}, *args, **kwargs):
         """Updates the status of the review request.
 
         The only supported update to a review request's resource is to change
@@ -1028,7 +1040,8 @@ class ReviewRequestResource(MarkdownFieldsMixin, WebAPIResource):
                             request.user,
                             close_description,
                             rich_text=close_description_rich_text,
-                            automated=automated)
+                            automated=automated,
+                            automated_service_info=automated_service_info)
                     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 f499615331187ffdfed1179f44aca5c1451f09e3..9508ec38956f1de5a493c45ef3cf7de356abc2ec 100644
--- a/reviewboard/webapi/resources/review_request_draft.py
+++ b/reviewboard/webapi/resources/review_request_draft.py
@@ -200,6 +200,12 @@ class ReviewRequestDraftResource(MarkdownFieldsMixin, WebAPIResource):
             'description': 'Whether or not the review request is updated '
                            'automatically.',
         },
+        'automated_service_info': {
+            'type': DictFieldType,
+            'description': 'Data that describes the details of the '
+                           'service that updated the review request '
+                           'automatically.',
+        },
         'branch': {
             'type': StringFieldType,
             'description': 'The new branch name.',
@@ -437,6 +443,7 @@ class ReviewRequestDraftResource(MarkdownFieldsMixin, WebAPIResource):
                trivial=None,
                publish_as_owner=False,
                automated=None,
+               automated_service_info={},
                extra_fields={},
                *args,
                **kwargs):
@@ -743,7 +750,9 @@ class ReviewRequestDraftResource(MarkdownFieldsMixin, WebAPIResource):
 
             try:
                 review_request.publish(user=publish_user, trivial=trivial,
-                                       automated=automated)
+                                       automated=automated,
+                                       automated_service_info=(
+                                           automated_service_info))
             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 0f6777cc8e05d90a63b13aee80eb00ac6f21c8aa..2a152d96d50eaa8fadbb6cf01ef688813b9067a5 100644
--- a/reviewboard/webapi/tests/test_review_request.py
+++ b/reviewboard/webapi/tests/test_review_request.py
@@ -1,4 +1,6 @@
 import kgb
+import json
+
 from django.contrib import auth
 from django.contrib.auth.models import User, Permission
 from django.db import IntegrityError
@@ -2129,6 +2131,7 @@ class ResourceItemTests(ExtraDataItemMixin, BaseWebAPITestCase,
         c = r.changedescs.latest('timestamp')
         self.assertEqual(c.text, 'comment')
         self.assertIsNone(c.extra_data.get('automated'))
+        self.assertIsNone(c.extra_data.get('automated_service_info'))
 
         fc_status = c.fields_changed['status']
         self.assertEqual(fc_status['old'][0], 'P')
@@ -2136,16 +2139,22 @@ class ResourceItemTests(ExtraDataItemMixin, BaseWebAPITestCase,
 
     def test_put_status_discarded_with_automated(self):
         """Testing the PUT review-requests/<id>/?status=discarded API
-        with automated=True
+        with automated=True and automated_service_info
         """
         r = self.create_review_request(submitter=self.user, publish=True)
 
+        automated_service_info = {
+            'service_name': 'automated_service',
+            'id': 1,
+        }
+
         rsp = self.api_put(
             get_review_request_item_url(r.display_id),
             {
                 'status': 'discarded',
                 'close_description': 'comment',
                 'automated': True,
+                'automated_service_info': json.dumps(automated_service_info),
             },
             expected_mimetype=review_request_item_mimetype)
 
@@ -2157,6 +2166,8 @@ class ResourceItemTests(ExtraDataItemMixin, BaseWebAPITestCase,
         c = r.changedescs.latest('timestamp')
         self.assertEqual(c.text, 'comment')
         self.assertTrue(c.extra_data.get('automated'))
+        self.assertEqual(c.extra_data.get('automated_service_info'),
+                         automated_service_info)
 
         fc_status = c.fields_changed['status']
         self.assertEqual(fc_status['old'][0], 'P')
@@ -2213,6 +2224,7 @@ class ResourceItemTests(ExtraDataItemMixin, BaseWebAPITestCase,
         c = r.changedescs.latest('timestamp')
         self.assertEqual(c.text, 'comment')
         self.assertIsNone(c.extra_data.get('automated'))
+        self.assertIsNone(c.extra_data.get('automated_service_info'))
 
         fc_status = c.fields_changed['status']
         self.assertEqual(fc_status['old'][0], 'P')
@@ -2220,16 +2232,22 @@ class ResourceItemTests(ExtraDataItemMixin, BaseWebAPITestCase,
 
     def test_put_status_submitted_with_automated(self):
         """Testing the PUT review-requests/<id>/?status=submitted API
-        with automated=True
+        with automated=True and automated_service_info
         """
         r = self.create_review_request(submitter=self.user, publish=True)
 
+        automated_service_info = {
+            'service_name': 'automated_service',
+            'id': 1,
+        }
+
         rsp = self.api_put(
             get_review_request_item_url(r.display_id),
             {
                 'status': 'submitted',
                 'close_description': 'comment',
                 'automated': True,
+                'automated_service_info': json.dumps(automated_service_info),
             },
             expected_mimetype=review_request_item_mimetype)
 
@@ -2241,6 +2259,8 @@ class ResourceItemTests(ExtraDataItemMixin, BaseWebAPITestCase,
         c = r.changedescs.latest('timestamp')
         self.assertEqual(c.text, 'comment')
         self.assertTrue(c.extra_data.get('automated'))
+        self.assertEqual(c.extra_data.get('automated_service_info'),
+                         automated_service_info)
 
         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 677f7d5a3db6e030ec5c605b0c3597f9fe6cdf7c..79cb76af9da4ec2ffdf69db3e02191baf6b80100 100644
--- a/reviewboard/webapi/tests/test_review_request_draft.py
+++ b/reviewboard/webapi/tests/test_review_request_draft.py
@@ -1,3 +1,5 @@
+import json
+
 from django.contrib import auth
 from django.contrib.auth.models import Permission, User
 from django.core import mail
@@ -444,7 +446,7 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
 
     def test_post_publish_with_automated(self):
         """Testing the POST review-requests/<id>/draft/?public=1 API with
-        automated=True
+        automated=True and automated_service_info
         """
         user = User.objects.get(username='doc')
         review_request = self.create_review_request(submitter=self.user,
@@ -454,12 +456,18 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
         self.spy_on(review_request_publishing.send)
         self.spy_on(review_request_published.send)
 
+        automated_service_info = {
+            'service_name': 'automated_service',
+            'id': 1,
+        }
+
         rsp = self.api_post(
             get_review_request_draft_url(review_request),
             {
                 'summary': 'New summary',
                 'public': True,
                 'automated': True,
+                'automated_service_info': json.dumps(automated_service_info),
             },
             expected_mimetype=review_request_draft_item_mimetype)
 
@@ -471,16 +479,20 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
 
         changedesc = review_request.changedescs.latest()
         self.assertTrue(changedesc.extra_data.get('automated'))
+        self.assertEqual(changedesc.extra_data.get('automated_service_info'),
+                         automated_service_info)
 
         self.assertSpyCalledWith(review_request_publishing.send,
-                                 automated=True)
+                                 automated=True,
+                                 automated_service_info=automated_service_info)
         self.assertSpyCalledWith(review_request_published.send,
                                  changedesc=changedesc,
-                                 automated=True)
+                                 automated=True,
+                                 automated_service_info=automated_service_info)
 
     def test_post_publish_not_automated(self):
         """Testing the POST review-requests/<id>/draft/?public=1 API without
-        automated=True
+        automated=True and automated_service_info
         """
         user = User.objects.get(username='doc')
         review_request = self.create_review_request(submitter=self.user,
@@ -506,12 +518,15 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
 
         changedesc = review_request.changedescs.latest()
         self.assertIsNone(changedesc.extra_data.get('automated'))
+        self.assertIsNone(changedesc.extra_data.get('automated_service_info'))
 
         self.assertSpyCalledWith(review_request_publishing.send,
-                                 automated=None)
+                                 automated=None,
+                                 automated_service_info={})
         self.assertSpyCalledWith(review_request_published.send,
                                  changedesc=changedesc,
-                                 automated=None)
+                                 automated=None,
+                                 automated_service_info={})
 
     #
     # HTTP PUT tests
@@ -1505,6 +1520,7 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
 
         changedesc = review_request.changedescs.latest()
         self.assertIsNone(changedesc.extra_data.get('automated'))
+        self.assertIsNone(changedesc.extra_data.get('automated_service_info'))
 
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(
@@ -1514,7 +1530,7 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
 
     def test_put_publish_with_automated(self):
         """Testing the PUT review-requests/<id>/draft/?public=1 API with
-        automated=True
+        automated=True and automated_service_info
         """
         # 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
@@ -1539,11 +1555,18 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
             # clear the outbox.
             mail.outbox = []
 
+            automated_service_info = {
+                'service_name': 'automated_service',
+                'id': 1,
+            }
+
             rsp = self.api_put(
                 get_review_request_draft_url(review_request),
                 {
                     'public': True,
                     'automated': True,
+                    'automated_service_info':
+                        json.dumps(automated_service_info),
                 },
                 expected_mimetype=review_request_draft_item_mimetype)
 
@@ -1558,6 +1581,8 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
 
         changedesc = review_request.changedescs.latest()
         self.assertTrue(changedesc.extra_data.get('automated'))
+        self.assertEqual(changedesc.extra_data.get('automated_service_info'),
+                         automated_service_info)
 
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(
@@ -1566,10 +1591,12 @@ class ResourceTests(SpyAgency, ExtraDataListMixin, ExtraDataItemMixin,
         self.assertValidRecipients(["doc", "grumpy"])
 
         self.assertSpyCalledWith(review_request_publishing.send,
-                                 automated=True)
+                                 automated=True,
+                                 automated_service_info=automated_service_info)
         self.assertSpyCalledWith(review_request_published.send,
                                  changedesc=changedesc,
-                                 automated=True)
+                                 automated=True,
+                                 automated_service_info=automated_service_info)
 
     def test_put_publish_with_new_submitter(self):
         """Testing the PUT review-requests/<id>/draft/?public=1 API
