diff --git a/opentech/apply/activity/admin.py b/opentech/apply/activity/admin.py
index e979c14a171a19eadae6a2a239806d5762c67c3c..9b9141baf43121b1d1ba3d04fde51aa917078c1d 100644
--- a/opentech/apply/activity/admin.py
+++ b/opentech/apply/activity/admin.py
@@ -4,7 +4,7 @@ from .models import Event, Message
 
 class MessageInline(admin.TabularInline):
     model = Message
-    readonly_fields = ('type', 'recipient', 'content')
+    readonly_fields = ('type', 'recipient', 'content', 'status')
     can_delete = False
 
     def has_add_permission(self, request):
diff --git a/opentech/apply/activity/messaging.py b/opentech/apply/activity/messaging.py
index b92445ff6693037828271e9463a9b5bcaf4a4d99..f570de8c06f949fa4a27d6c4a7a3d73114175372 100644
--- a/opentech/apply/activity/messaging.py
+++ b/opentech/apply/activity/messaging.py
@@ -70,14 +70,12 @@ class AdapterBase:
 
         for recipient in self.recipients(message_type, **kwargs):
             if settings.SEND_MESSAGES or self.always_send:
-                self.send_message(message, recipient=recipient, **kwargs)
-                from.models import Message
-                Message.objects.create(
-                    type=self.adapter_type,
-                    content=message,
-                    recipient=recipient,
-                    event=event,
-                )
+                status = self.send_message(message, recipient=recipient, **kwargs)
+            else:
+                status = 'Message not sent as SEND_MESSAGES==FALSE'
+
+            if status:
+                self.log_message(message, recipient, event, status)
 
             if not settings.SEND_MESSAGES:
                 if recipient:
@@ -86,7 +84,19 @@ class AdapterBase:
                     message = '{}: {}'.format(self.adapter_type, message)
                 messages.add_message(kwargs['request'], messages.INFO, message)
 
+    def log_message(self, message, recipient, event, status):
+        from.models import Message
+        Message.objects.create(
+            type=self.adapter_type,
+            content=message,
+            recipient=recipient,
+            event=event,
+            status=status,
+        )
+
     def send_message(self, message, **kwargs):
+        # Process the message, should return the result of the send
+        # Returning None will not record this action
         raise NotImplementedError()
 
 
@@ -182,7 +192,12 @@ class SlackAdapter(AdapterBase):
 
     def send_message(self, message, recipient, **kwargs):
         if not self.destination or not self.target_room:
-            return
+            errors = list()
+            if not self.destination:
+                errors.append('Destination URL')
+            if not self.target_room:
+                errors.append('Room ID')
+            return 'Missing configuration: {}'.format(', '.join(errors))
 
         message = ' '.join([recipient, message]).strip()
 
@@ -190,7 +205,7 @@ class SlackAdapter(AdapterBase):
             "room": self.target_room,
             "message": message,
         }
-        requests.post(self.destination, json=data)
+        return requests.post(self.destination, json=data)
 
 
 class EmailAdapter(AdapterBase):
@@ -215,7 +230,8 @@ class EmailAdapter(AdapterBase):
 
     def notify_comment(self, **kwargs):
         comment = kwargs['comment']
-        if not comment.private:
+        submission = kwargs['submission']
+        if not comment.private and not comment.user == submission.user:
             return self.render_message('messages/email/comment.html', **kwargs)
 
     def recipients(self, message_type, submission, **kwargs):
@@ -234,7 +250,7 @@ class EmailAdapter(AdapterBase):
         return render_to_string(template, kwargs)
 
     def send_message(self, message, submission, subject, recipient, **kwargs):
-        send_mail(
+        return send_mail(
             subject,
             message,
             submission.page.specific.from_address,
diff --git a/opentech/apply/activity/migrations/0007_message_status.py b/opentech/apply/activity/migrations/0007_message_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..64789fcf701a87958f36728fd6e26b2a7462bb49
--- /dev/null
+++ b/opentech/apply/activity/migrations/0007_message_status.py
@@ -0,0 +1,19 @@
+# Generated by Django 2.0.2 on 2018-07-30 14:09
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('activity', '0006_message'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='message',
+            name='status',
+            field=models.TextField(default=''),
+            preserve_default=False,
+        ),
+    ]
diff --git a/opentech/apply/activity/models.py b/opentech/apply/activity/models.py
index 41b970b558188f18ce2c0ca0d7c4bc5487bc1ec1..7970cf9eb5fe928833f5bf5a38eb4da0466c5227 100644
--- a/opentech/apply/activity/models.py
+++ b/opentech/apply/activity/models.py
@@ -120,3 +120,4 @@ class Message(models.Model):
     content = models.TextField()
     recipient = models.CharField(max_length=250)
     event = models.ForeignKey(Event, on_delete=models.CASCADE)
+    status = models.TextField()
diff --git a/opentech/apply/activity/templates/messages/email/applicant_base.html b/opentech/apply/activity/templates/messages/email/applicant_base.html
index 4405cab7b2cb888abf87d6418b4a630f830063d6..a629768ef022ed0c9e8d3ac9750ac05068778e1e 100644
--- a/opentech/apply/activity/templates/messages/email/applicant_base.html
+++ b/opentech/apply/activity/templates/messages/email/applicant_base.html
@@ -1,6 +1,5 @@
-{% block salutation %}
-Dear {{ submission.user.get_full_name|default:"applicant" }},
-{% endblock %}
+{% extends "messages/email/base.html" %}
+{% block salutation %}Dear {{ submission.user.get_full_name|default:"applicant" }},{% endblock %}
 
 {% block more_info %}
 You can access your application here: {{ request.scheme }}://{{ request.get_host }}{{ submission.get_absolute_url }}
diff --git a/opentech/apply/activity/templates/messages/email/comment.html b/opentech/apply/activity/templates/messages/email/comment.html
index 845e7fe7598a0f7a421da871e13bcf2d6f849304..c092483e37e1ed63db7ece92aa2fd89938793761 100644
--- a/opentech/apply/activity/templates/messages/email/comment.html
+++ b/opentech/apply/activity/templates/messages/email/comment.html
@@ -1,7 +1,5 @@
 {% extends "messages/email/applicant_base.html" %}
 
-{% block content %}
-There has been a new comment on your application: {{ submission.title }}
+{% block content %}There has been a new comment on your application: {{ submission.title }}
 
-{{ comment.message }}
-{% endblock %}
+{{ comment.user }}: {{ comment.message }}{% endblock %}
diff --git a/opentech/apply/activity/templates/messages/email/ready_to_review.html b/opentech/apply/activity/templates/messages/email/ready_to_review.html
index bef3718e392b9469c8841a78749538bb794f52d6..b7ec4973950cb081dfefb2b01bb89fd0830d00e1 100644
--- a/opentech/apply/activity/templates/messages/email/ready_to_review.html
+++ b/opentech/apply/activity/templates/messages/email/ready_to_review.html
@@ -1,5 +1,5 @@
 {% extends "messages/email/base.html" %}
-{% block salutation %}Dear Reviewer{% endblock %}
+{% block salutation %}Dear Reviewer,{% endblock %}
 
 {% block content %}
 A new proposal has been added to your review list.
diff --git a/opentech/apply/activity/tests/factories.py b/opentech/apply/activity/tests/factories.py
new file mode 100644
index 0000000000000000000000000000000000000000..89c491bff1ab994b2c2386cd4cfdb5f7f61b9e1a
--- /dev/null
+++ b/opentech/apply/activity/tests/factories.py
@@ -0,0 +1,31 @@
+import factory
+
+from opentech.apply.activity.models import Activity, Event, INTERNAL, MESSAGES, REVIEWER
+from opentech.apply.funds.tests.factories import ApplicationSubmissionFactory
+from opentech.apply.users.tests.factories import UserFactory
+
+
+class CommentFactory(factory.DjangoModelFactory):
+    class Meta:
+        model = Activity
+
+    class Params:
+        internal = factory.Trait(visibility=INTERNAL)
+        reviewers = factory.Trait(visibility=REVIEWER)
+
+    submission = factory.SubFactory(ApplicationSubmissionFactory)
+    user = factory.SubFactory(UserFactory)
+    message = factory.Faker('sentence')
+
+    @classmethod
+    def _get_manager(cls, model_class):
+        return model_class.comments
+
+
+class EventFactory(factory.DjangoModelFactory):
+    class Meta:
+        model = Event
+
+    type = factory.Iterator([choice[0] for choice in MESSAGES.choices()])
+    by = factory.SubFactory(UserFactory)
+    submission = factory.SubFactory(ApplicationSubmissionFactory)
diff --git a/opentech/apply/activity/tests/test_messaging.py b/opentech/apply/activity/tests/test_messaging.py
index f05467316f50a02b8f48ada2356b6fe983c46c61..1b22b3317c8e1b559d3ad56ec74bb2f41188bfbc 100644
--- a/opentech/apply/activity/tests/test_messaging.py
+++ b/opentech/apply/activity/tests/test_messaging.py
@@ -20,6 +20,7 @@ from ..messaging import (
     MESSAGES,
     SlackAdapter,
 )
+from .factories import CommentFactory, EventFactory
 
 
 class TestAdapter(AdapterBase):
@@ -36,9 +37,30 @@ class TestAdapter(AdapterBase):
     def recipients(self, message_type, **kwargs):
         return [message_type]
 
+    def log_message(self, message, recipient, event, status):
+        pass
+
+
+class AdapterMixin:
+    adapter = None
+
+    def process_kwargs(self, **kwargs):
+        if 'user' not in kwargs:
+            kwargs['user'] = UserFactory()
+        if 'submission' not in kwargs:
+            kwargs['submission'] = ApplicationSubmissionFactory()
+        if 'request' not in kwargs:
+            kwargs['request'] = None
+
+        return kwargs
+
+    def adapter_process(self, message_type, **kwargs):
+        kwargs = self.process_kwargs(**kwargs)
+        self.adapter.process(message_type, event=EventFactory(submission=kwargs['submission']), **kwargs)
+
 
 @override_settings(SEND_MESSAGES=True)
-class TestBaseAdapter(TestCase):
+class TestBaseAdapter(AdapterMixin, TestCase):
     def setUp(self):
         patched_class = patch.object(TestAdapter, 'send_message')
         self.mock_adapter = patched_class.start()
@@ -47,55 +69,61 @@ class TestBaseAdapter(TestCase):
 
     def test_can_send_a_message(self):
         message_type = MESSAGES.UPDATE_LEAD
-        self.adapter.process(message_type)
+        self.adapter_process(message_type)
 
-        self.adapter.send_message.assert_called_once_with(message_type.value, recipient=message_type)
+        self.adapter.send_message.assert_called_once()
+        self.assertEqual(self.adapter.send_message.call_args[0], (message_type.value,))
 
     def test_doesnt_send_a_message_if_not_configured(self):
-        self.adapter.process('this_is_not_a_message_type')
+        self.adapter_process('this_is_not_a_message_type')
 
         self.adapter.send_message.assert_not_called()
 
     def test_calls_method_if_avaliable(self):
         method_name = 'new_method'
         return_message = 'Returned message'
-        setattr(self.adapter, method_name, lambda: return_message)
+        setattr(self.adapter, method_name, lambda **kw: return_message)
         self.adapter.messages[method_name] = method_name
 
-        self.adapter.process(method_name)
+        self.adapter_process(method_name)
 
-        self.adapter.send_message.assert_called_once_with(return_message, recipient=method_name)
+        self.adapter.send_message.assert_called_once()
+        self.assertEqual(self.adapter.send_message.call_args[0], (return_message,))
 
     def test_that_kwargs_passed_to_send_message(self):
         message_type = MESSAGES.UPDATE_LEAD
         kwargs = {'test': 'that', 'these': 'exist'}
-        self.adapter.process(message_type, **kwargs)
+        self.adapter_process(message_type, **kwargs)
 
-        self.adapter.send_message.assert_called_once_with(message_type.value, recipient=message_type, **kwargs)
+        self.adapter.send_message.assert_called_once()
+        for key in kwargs:
+            self.assertTrue(key in self.adapter.send_message.call_args[1])
 
     def test_that_message_is_formatted(self):
         message_type = MESSAGES.UPDATE_LEAD
         message = 'message value'
 
         with patch.dict(self.adapter.messages, {message_type: '{message_to_format}'}):
-            self.adapter.process(message_type, message_to_format=message)
+            self.adapter_process(message_type, message_to_format=message)
 
-        self.adapter.send_message.assert_called_once_with(message, message_to_format=message, recipient=message_type)
+        self.adapter.send_message.assert_called_once()
+        self.assertEqual(self.adapter.send_message.call_args[0], (message,))
 
     def test_can_include_extra_kwargs(self):
         message_type = MESSAGES.UPDATE_LEAD
 
         with patch.dict(self.adapter.messages, {message_type: '{extra}'}):
             with patch.object(self.adapter, 'extra_kwargs', return_value={'extra': 'extra'}):
-                self.adapter.process(message_type)
+                self.adapter_process(message_type)
 
-        self.adapter.send_message.assert_called_once_with('extra', extra='extra', recipient=message_type)
+        self.adapter.send_message.assert_called_once()
+        self.assertTrue('extra' in self.adapter.send_message.call_args[1])
 
     @override_settings(SEND_MESSAGES=False)
     def test_django_messages_used(self):
         request = make_request()
 
-        self.adapter.process(MESSAGES.UPDATE_LEAD, request=request)
+        self.adapter_process(MESSAGES.UPDATE_LEAD, request=request)
 
         messages = list(get_messages(request))
         self.assertEqual(len(messages), 1)
@@ -107,7 +135,11 @@ class TestMessageBackend(TestCase):
     def setUp(self):
         self.mocked_adapter = Mock(AdapterBase)
         self.backend = MessengerBackend
-        self.kwargs = {'request': None, 'user': None, 'submission': None}
+        self.kwargs = {
+            'request': None,
+            'user': UserFactory(),
+            'submission': ApplicationSubmissionFactory(),
+        }
 
     def test_message_sent_to_adapter(self):
         adapter = self.mocked_adapter()
@@ -115,7 +147,7 @@ class TestMessageBackend(TestCase):
 
         messenger(MESSAGES.UPDATE_LEAD, **self.kwargs)
 
-        adapter.process.assert_called_once_with(MESSAGES.UPDATE_LEAD, **self.kwargs)
+        adapter.process.assert_called_once_with(MESSAGES.UPDATE_LEAD, Event.objects.first(), **self.kwargs)
 
     def test_message_sent_to_all_adapter(self):
         adapters = [self.mocked_adapter(), self.mocked_adapter()]
@@ -135,8 +167,8 @@ class TestMessageBackend(TestCase):
         messenger(MESSAGES.UPDATE_LEAD, **self.kwargs)
 
         self.assertEqual(Event.objects.count(), 1)
-        self.assertEqual(Event.objects.first().type, MESSAGES.UPDATE_LEAD)
-        self.assertEqual(Event.objects.first().get_type_display, MESSAGES.UPDATE_LEAD.value)
+        self.assertEqual(Event.objects.first().type, MESSAGES.UPDATE_LEAD.name)
+        self.assertEqual(Event.objects.first().get_type_display(), MESSAGES.UPDATE_LEAD.value)
         self.assertEqual(Event.objects.first().by, user)
 
 
@@ -240,20 +272,33 @@ class TestSlackAdapter(TestCase):
 
 
 @override_settings(SEND_MESSAGES=True)
-class TestEmailAdapter(TestCase):
+class TestEmailAdapter(AdapterMixin, TestCase):
+    adapter = EmailAdapter()
+
     def test_email_new_submission(self):
-        adapter = EmailAdapter()
         submission = ApplicationSubmissionFactory()
-        adapter.process(MESSAGES.NEW_SUBMISSION, submission=submission)
+        self.adapter_process(MESSAGES.NEW_SUBMISSION, submission=submission)
 
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].to, [submission.user.email])
 
+    def test_no_email_private_comment(self):
+        comment = CommentFactory(internal=True)
+
+        self.adapter_process(MESSAGES.COMMENT, comment=comment, submission=comment.submission)
+        self.assertEqual(len(mail.outbox), 0)
+
+    def test_no_email_own_comment(self):
+        application = ApplicationSubmissionFactory()
+        comment = CommentFactory(user=application.user, submission=application)
+
+        self.adapter_process(MESSAGES.COMMENT, comment=comment, user=comment.user, submission=comment.submission)
+        self.assertEqual(len(mail.outbox), 0)
+
     def test_reviewers_email(self):
-        adapter = EmailAdapter()
         reviewers = ReviewerFactory.create_batch(4)
         submission = ApplicationSubmissionFactory(status='external_review', reviewers=reviewers, workflow_stages=2)
-        adapter.process(MESSAGES.READY_FOR_REVIEW, submission=submission)
+        self.adapter_process(MESSAGES.READY_FOR_REVIEW, submission=submission)
 
         self.assertEqual(len(mail.outbox), 4)
         self.assertTrue(mail.outbox[0].subject, 'ready to review')
diff --git a/opentech/apply/stream_forms/testing/factories.py b/opentech/apply/stream_forms/testing/factories.py
index 50d5f7d774c8c767f71d738e923213cb11f1beed..1d03a5670b7a3c7978245cb81a10ae1a8698949b 100644
--- a/opentech/apply/stream_forms/testing/factories.py
+++ b/opentech/apply/stream_forms/testing/factories.py
@@ -1,3 +1,4 @@
+from collections import defaultdict
 import json
 import uuid
 
@@ -136,8 +137,10 @@ class MultiFileFieldBlockFactory(UploadableMediaFactory):
 
 
 class StreamFieldUUIDFactory(wagtail_factories.StreamFieldFactory):
-    def generate(self, *args, **kwargs):
-        blocks = super().generate(*args, **kwargs)
+    def generate(self, step, params):
+        if not params:
+            params = self.build_form(params)
+        blocks = super().generate(step, params)
         ret_val = list()
         # Convert to JSON so we can add id before create
         for block_name, value in blocks:
@@ -145,3 +148,14 @@ class StreamFieldUUIDFactory(wagtail_factories.StreamFieldFactory):
             value = block.get_prep_value(value)
             ret_val.append({'type': block_name, 'value': value, 'id': str(uuid.uuid4())})
         return json.dumps(ret_val)
+
+    def build_form(self, data):
+        extras = defaultdict(dict)
+
+        form_fields = {}
+        for i, field in enumerate(self.factories):
+            form_fields[f'{i}__{field}__'] = ''
+            for attr, value in extras[field].items():
+                form_fields[f'{i}__{field}__{attr}'] = value
+
+        return form_fields