diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po
index 936e656c0c45a3ca1badd9de8c41d9af534f2282..c706c6d1263ae6e9850b1a1cf65965e8134758f7 100644
--- a/src/core/locale/de/LC_MESSAGES/django.po
+++ b/src/core/locale/de/LC_MESSAGES/django.po
@@ -375,6 +375,18 @@ msgstr "Zeitzone in der die Konfernez veranstaltet wird"
 msgid "Conference__timezone"
 msgstr "Zeitzone"
 
+msgid "Conference__send_pn_disabled__help"
+msgstr "PN-Versand in dieser Konferenz deaktivieren"
+
+msgid "Conference__send_pn_disabled"
+msgstr "PNs deaktiviert"
+
+msgid "Conference__board_disabled__help"
+msgstr "Einträge im Board erstellen / bearbeiten deaktiviert"
+
+msgid "Conference__board_disabled"
+msgstr "Bulletin Board deaktiviert"
+
 msgid "Conference__support_clusters__help"
 msgstr "Assemblies können sich zu Clustern gruppieren"
 
@@ -1095,6 +1107,12 @@ msgstr "im WorkAdventure wird Video von anderen Teilnehmern gezeigt"
 msgid "PlatformUser__receive_video"
 msgstr "Video-Chat"
 
+msgid "PlatformUser__shadow_banned__help"
+msgstr "User kann PNs verschicken aber die Empfänger werden diese nie zu sehen bekommen"
+
+msgid "PlatformUser__shadow_banned"
+msgstr "Shadowbanned"
+
 msgid "PlatformUser__autoaccept_contacts__help"
 msgstr "Kontaktanfragen automatisch akzeptieren"
 
diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po
index 7037487469959983ca8f0ba7da1935ff3c512554..ab9303974afc4609226cd3b6f21d3e05569828c9 100644
--- a/src/core/locale/en/LC_MESSAGES/django.po
+++ b/src/core/locale/en/LC_MESSAGES/django.po
@@ -381,6 +381,18 @@ msgstr "time zone in which this conference is operated"
 msgid "Conference__timezone"
 msgstr "time zone"
 
+msgid "Conference__send_pn_disabled__help"
+msgstr "disable sending of private messages in this conference"
+
+msgid "Conference__send_pn_disabled"
+msgstr "disable PMs"
+
+msgid "Conference__board_disabled__help"
+msgstr "creating / editing board entries disabled"
+
+msgid "Conference__board_disabled"
+msgstr "disable bulletin board"
+
 msgid "Conference__support_clusters__help"
 msgstr "allow assemblies to cluster in groups"
 
@@ -1101,6 +1113,12 @@ msgstr "see video from other participants in e.g. WorkAdventure"
 msgid "PlatformUser__receive_video"
 msgstr "video chat"
 
+msgid "PlatformUser__shadow_banned__help"
+msgstr "User will be able to send DMs but recipients won't see them"
+
+msgid "PlatformUser__shadow_banned"
+msgstr "shadow banned"
+
 msgid "PlatformUser__autoaccept_contacts__help"
 msgstr "automatically accept incoming contact requests"
 
diff --git a/src/core/migrations/0055_dm_restrictions.py b/src/core/migrations/0055_dm_restrictions.py
new file mode 100644
index 0000000000000000000000000000000000000000..e898a9e2bafa5156d65afb7785ed830a9b1afeb3
--- /dev/null
+++ b/src/core/migrations/0055_dm_restrictions.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.1.4 on 2020-12-29 00:05
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0054_UserBadgeGrammar'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='conference',
+            name='board_disabled',
+            field=models.BooleanField(default=False, help_text='Conference__board_disabled__help', verbose_name='Conference__board_disabled'),
+        ),
+        migrations.AddField(
+            model_name='conference',
+            name='send_pn_disabled',
+            field=models.BooleanField(default=False, help_text='Conference__send_pn_disabled__help', verbose_name='Conference__send_pn_disabled'),
+        ),
+        migrations.AddField(
+            model_name='platformuser',
+            name='shadow_banned',
+            field=models.BooleanField(default=False, help_text='PlatformUser__shadow_banned__help', verbose_name='PlatformUser__shadow_banned'),
+        ),
+    ]
diff --git a/src/core/models/conference.py b/src/core/models/conference.py
index 922246c2f1b6a2113fab09dc3c9041e8a4b91c19..e15c0900e11035ed1bb1a5fa2108a05a29da07ef 100644
--- a/src/core/models/conference.py
+++ b/src/core/models/conference.py
@@ -160,6 +160,15 @@ class Conference(models.Model):
         help_text=_('Conference__timezone__help'),
         verbose_name=_('Conference__timezone'))
 
+    send_pn_disabled = models.BooleanField(
+        default=False,
+        help_text=_('Conference__send_pn_disabled__help'),
+        verbose_name=_('Conference__send_pn_disabled'))
+    board_disabled = models.BooleanField(
+        default=False,
+        help_text=_('Conference__board_disabled__help'),
+        verbose_name=_('Conference__board_disabled'))
+
     support_clusters = models.BooleanField(
         default=True,
         help_text=_('Conference__support_clusters__help'),
diff --git a/src/core/models/users.py b/src/core/models/users.py
index 88b362e36b073bd636bde48e19e5da5af06aaa25..e11667e6f8d99217ad924bbedead48b49b073b1c 100644
--- a/src/core/models/users.py
+++ b/src/core/models/users.py
@@ -114,6 +114,12 @@ class PlatformUser(AbstractUser):
         help_text=_('PlatformUser__receive_video__help'),
         verbose_name=_('PlatformUser__receive_video'))
 
+    # Administrative
+    shadow_banned = models.BooleanField(
+        default=False,
+        help_text=_('PlatformUser__shadow_banned__help'),
+        verbose_name=_('PlatformUser__shadow_banned'))
+
     audio_muted = models.BooleanField(default=False)
     audio_volume = models.FloatField(blank=True, null=True)
 
diff --git a/src/plainui/forms.py b/src/plainui/forms.py
index 3a91ebcc548fa04e793ef36b3496677bd0e077c3..9be1421737831356d2589ce9b00962d363af8d50 100644
--- a/src/plainui/forms.py
+++ b/src/plainui/forms.py
@@ -25,17 +25,35 @@ class UsernameField(forms.CharField):
 
 
 class NewDirectMessageForm(forms.Form):
+    def __init__(self, conf, user, *args, **kwargs):
+        self.conf = conf
+        self.user = user
+        super().__init__(*args, **kwargs)
+
     in_reply_to = forms.UUIDField(required=False)
     recipient = UsernameField(widget=forms.TextInput(attrs={'placeholder': _("Please enter the recipient name")}))
     subject = forms.CharField(max_length=200, min_length=1, strip=True, widget=forms.TextInput(attrs={'placeholder': _("Please enter a subject")}))
     body = forms.CharField(widget=forms.Textarea)
 
+    def clean(self):
+        if self.conf.send_pn_disabled:
+            raise ValidationError(_("Sending Messages is currently disabled"))
+
 
 class BulletinBoardEntryForm(forms.Form):
+    def __init__(self, conf, user, *args, **kwargs):
+        self.conf = conf
+        self.user = user
+        super().__init__(*args, **kwargs)
+
     title = forms.CharField(max_length=200, min_length=1, strip=True, widget=forms.TextInput(attrs={'placeholder': _("Please enter a title")}))
     is_public = forms.BooleanField(required=False)
     text = forms.CharField(widget=forms.Textarea)
 
+    def clean(self):
+        if self.conf.board_disabled:
+            raise ValidationError(_("Bulletin Board is currently disabled"))
+
 
 class ExampleForm(forms.Form):
     """ used in the component gallery """
@@ -247,7 +265,7 @@ class ReportForm(forms.Form):
         if kind == 'url':
             reported_content = self.cleaned_data['kind_data']
         elif kind == 'pn':
-            dm = DirectMessage.objects.get(pk=self.cleaned_data['kind_data'], recipient=request.user)
+            dm = DirectMessage.objects.get(pk=self.cleaned_data['kind_data'])
             reported_content = 'DM by %(sender)s (%(sender_id)s) to %(recipient)s (%(recipient_id)s) at %(timestamp)s (%(uuid)s).\n'
             reported_content += 'Subject: "%(subject)s"\nMessage: "%(body)s"'
             reported_content %= {
diff --git a/src/plainui/locale/de/LC_MESSAGES/django.po b/src/plainui/locale/de/LC_MESSAGES/django.po
index fde3b102317a7b2c4f28d8e970c3cf469a1e2df9..61976732b7d7f66f30777d1b95707c5b035cd3f7 100644
--- a/src/plainui/locale/de/LC_MESSAGES/django.po
+++ b/src/plainui/locale/de/LC_MESSAGES/django.po
@@ -27,9 +27,15 @@ msgstr "Bitte einen Empfänger angeben"
 msgid "Please enter a subject"
 msgstr "Bitte einen Betreff eingeben"
 
+msgid "Sending Messages is currently disabled"
+msgstr "Nachrichten versenden ist momentan deaktiviert"
+
 msgid "Please enter a title"
 msgstr "Bitte einen Titel eingeben"
 
+msgid "Bulletin Board is currently disabled"
+msgstr "Das Board ist momentan deaktiviert"
+
 msgid "Placeholder text"
 msgstr "Platzhalter text"
 
diff --git a/src/plainui/locale/en/LC_MESSAGES/django.po b/src/plainui/locale/en/LC_MESSAGES/django.po
index e4cdd8becb1211e87fc0f75599a14448442a896c..062a82002da542844fe87358968ef7a176b6f162 100644
--- a/src/plainui/locale/en/LC_MESSAGES/django.po
+++ b/src/plainui/locale/en/LC_MESSAGES/django.po
@@ -27,9 +27,15 @@ msgstr ""
 msgid "Please enter a subject"
 msgstr ""
 
+msgid "Sending Messages is currently disabled"
+msgstr ""
+
 msgid "Please enter a title"
 msgstr ""
 
+msgid "Bulletin Board is currently disabled"
+msgstr ""
+
 msgid "Placeholder text"
 msgstr ""
 
diff --git a/src/plainui/tests.py b/src/plainui/tests.py
index c344d4f6ab58d85554854d2c7a68ff7e22e61bd7..ad4649148ec3e55ae11ebdbed8c4c8d56d25fc0c 100644
--- a/src/plainui/tests.py
+++ b/src/plainui/tests.py
@@ -1033,7 +1033,7 @@ class ViewsTest(TestCase):
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['form']['recipient'].value(), 'blgl')
 
-    @override_settings(LANGUAGE_CODE='en')
+    @override_settings(LANGUAGE_CODE='en', AUTOBAN_KEYWORDS=['blork', re.compile('12.45')])
     def test_PersonalMessageSendView_post(self):
         user2 = PlatformUser(username='testuser2')
         user2.save()
@@ -1085,6 +1085,38 @@ class ViewsTest(TestCase):
         self.assertEqual(form.errors['subject'], ['This field is required.'])
         self.assertEqual(form.errors['body'], ['This field is required.'])
 
+        DirectMessage.objects.all().delete()
+        self.user.refresh_from_db()
+        self.assertFalse(self.user.shadow_banned)
+        self.assertEqual(len(mail.outbox), 0)
+        resp = self.client.post(reverse('plainui:personal_message_send', kwargs={'conf_slug': self.conf.slug}), {
+            'in_reply_to': '',
+            'recipient': 'testuser2',
+            'subject': 'blork',
+            'body': 'Body2',
+        })
+        self.assertRedirects(resp, reverse('plainui:personal_message', kwargs={'conf_slug': self.conf.slug}))
+        new_dm = DirectMessage.objects.get()
+        self.user.refresh_from_db()
+        self.assertTrue(self.user.shadow_banned)
+        self.assertTrue(new_dm.deleted_by_recipient, True)
+        self.assertEqual(len(mail.outbox), 1)
+
+        self.user.shadow_banned = False
+        self.user.save()
+        resp = self.client.post(reverse('plainui:personal_message_send', kwargs={'conf_slug': self.conf.slug}), {
+            'in_reply_to': '',
+            'recipient': 'testuser2',
+            'subject': 'this is ok',
+            'body': '12345',
+        })
+        self.assertRedirects(resp, reverse('plainui:personal_message', kwargs={'conf_slug': self.conf.slug}))
+        new_dm = DirectMessage.objects.get(subject='this is ok')
+        self.user.refresh_from_db()
+        self.assertTrue(self.user.shadow_banned)
+        self.assertTrue(new_dm.deleted_by_recipient, True)
+        self.assertEqual(len(mail.outbox), 2)
+
     def test_PersonalMessageShowView(self):
         user2 = PlatformUser(username='testuser2')
         user2.save()
@@ -1357,7 +1389,7 @@ class ViewsTest(TestCase):
         self.assertEqual(resp.context_data['form']['title'].value(), '')
         self.assertEqual(resp.context_data['form']['text'].value(), '')
 
-    @override_settings(LANGUAGE_CODE='en')
+    @override_settings(LANGUAGE_CODE='en', AUTOBAN_KEYWORDS=['foo 123', re.compile('asdf.foo')])
     def test_BoardEntryEditView_post(self):
         user2 = PlatformUser(username='testuser2')
         user2.save()
@@ -1412,6 +1444,33 @@ class ViewsTest(TestCase):
         self.assertEqual(new_entry.text, 'some texty text')
         self.assertSetsMessage(resp, 'Bulletin Board Entry created.')
 
+        BulletinBoardEntry.objects.all().delete()
+        self.user.refresh_from_db()
+        self.assertFalse(self.user.shadow_banned)
+        self.assertEqual(len(mail.outbox), 0)
+        self.assertNeedsLogin(reverse('plainui:board_entry_new', kwargs={'conf_slug': self.conf.slug}), post=True)
+        resp = self.client.post(reverse('plainui:board_entry_new', kwargs={'conf_slug': self.conf.slug}), {
+            'title': 'New Entry',
+            'text': 'foo 123',
+        })
+        new_entry = BulletinBoardEntry.objects.get()
+        self.assertRedirects(resp, reverse('plainui:board_entry_edit', kwargs={'conf_slug': self.conf.slug, 'id': str(new_entry.pk)}))
+        self.user.refresh_from_db()
+        self.assertTrue(self.user.shadow_banned)
+        self.assertEqual(len(mail.outbox), 1)
+
+        self.user.shadow_banned = False
+        self.user.save()
+        self.assertNeedsLogin(reverse('plainui:board_entry_edit', kwargs={'conf_slug': self.conf.slug, 'id': str(new_entry.pk)}), post=True)
+        resp = self.client.post(reverse('plainui:board_entry_edit', kwargs={'conf_slug': self.conf.slug, 'id': str(new_entry.pk)}), {
+            'title': 'asdf4foo',
+            'text': 'blblblblbl',
+        })
+        self.assertRedirects(resp, reverse('plainui:board_entry_edit', kwargs={'conf_slug': self.conf.slug, 'id': str(new_entry.pk)}))
+        self.user.refresh_from_db()
+        self.assertTrue(self.user.shadow_banned)
+        self.assertEqual(len(mail.outbox), 2)
+
     def test_BoardEntryDeleteView_get(self):
         self.assertNeedsLogin(reverse('plainui:board_entry_delete', kwargs={'conf_slug': self.conf.slug}))
         resp = self.client.get(reverse('plainui:board_entry_delete', kwargs={'conf_slug': self.conf.slug}))
diff --git a/src/plainui/utils.py b/src/plainui/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8455d1d8be8ba7cdae54fb1303ad51df3cff64b
--- /dev/null
+++ b/src/plainui/utils.py
@@ -0,0 +1,35 @@
+import re
+
+from django.conf import settings
+
+from .forms import ReportForm
+
+
+def check_message_content(conf, request, text, kind, kind_data):
+    try:
+        trigger_autoban = False
+        for pattern in settings.AUTOBAN_KEYWORDS:
+            if isinstance(pattern, re.Pattern):
+                trigger_autoban |= pattern.search(text) is not None
+            elif pattern in text:
+                trigger_autoban = True
+
+        if not trigger_autoban:
+            return False
+
+        request.user.shadow_banned = True
+        request.user.save(update_fields=['shadow_banned'])
+
+        report_form = ReportForm(conf=conf, data={
+            'kind': kind,
+            'kind_data': str(kind_data),
+            'category': 'abuse',
+            'message': 'Autoban triggered',
+            'message2': '.',
+        })
+        report_form.is_valid()
+
+        report_form.send_report_mail('plainui/registration/report_mail_body.txt', request)
+        return True
+    except Exception:
+        raise Exception("Boom")  # don't allow leaking eg. ValidationErrors that might be handled in a View
diff --git a/src/plainui/views.py b/src/plainui/views.py
index 5cf780429b44e6e6fffb3c317ad92e3bb574184a..7c03f25664cc5aaf3ab919f41168eadfa96a6606 100644
--- a/src/plainui/views.py
+++ b/src/plainui/views.py
@@ -37,6 +37,7 @@ from .forms import BulletinBoardEntryForm, ExampleForm, InputTokenForm, NewDirec
     ProfileEditForm, SelfOrganizedSessionForm, RedeemTokenAddToUserForm, RedeemTokenUserCreateForm, ReportForm, \
     RedeemBadgeForm, TokenPasswortResetForm
 from .models import BulletinBoardEntry
+from .utils import check_message_content
 
 
 def _session_refresh_favorite_assemblies(session, user) -> List[str]:
@@ -828,6 +829,12 @@ class PersonalMessageSendView(ConferenceRequiredMixin, FormView):
         initial['in_reply_to'] = self.request.POST.get('in_reply_to', self.request.GET.get('in_reply_to', ''))
         return initial
 
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+        kwargs['conf'] = self.conf
+        kwargs['user'] = self.request.user
+        return kwargs
+
     def get_success_url(self):
         return reverse('plainui:personal_message', kwargs={'conf_slug': self.kwargs['conf_slug']})
 
@@ -841,11 +848,16 @@ class PersonalMessageSendView(ConferenceRequiredMixin, FormView):
             in_reply_to_id=in_reply_to,
             subject=form.cleaned_data['subject'],
             body=form.cleaned_data['body'],
+            deleted_by_recipient=self.request.user != form.cleaned_data['recipient'] and self.request.user.shadow_banned,
         )
         dm.save()
         if in_reply_to:
             DirectMessage.objects.filter(id=in_reply_to, recipient=self.request.user).update(has_responded=True)
 
+        if check_message_content(self.conf, self.request, form.cleaned_data['subject'], 'pn', dm.pk) or \
+                check_message_content(self.conf, self.request, form.cleaned_data['body'], 'pn', dm.pk):
+            dm.deleted_by_recipient = True
+            dm.save(update_fields=['deleted_by_recipient'])
         messages.success(self.request, gettext("Message sent."))
         return super().form_valid(form)
 
@@ -1074,6 +1086,12 @@ class BoardEntryEditView(ConferenceRequiredMixin, FormView):
             **kwargs
         )
 
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+        kwargs['conf'] = self.conf
+        kwargs['user'] = self.request.user
+        return kwargs
+
     def get_initial(self):
         initial = super().get_initial()
         if 'id' in self.kwargs:
@@ -1094,6 +1112,9 @@ class BoardEntryEditView(ConferenceRequiredMixin, FormView):
         self.board_entry.text = form.cleaned_data['text']
         self.board_entry.save()
 
+        check_message_content(self.conf, self.request, form.cleaned_data['title'], 'board', self.board_entry.pk)
+        check_message_content(self.conf, self.request, form.cleaned_data['text'], 'board', self.board_entry.pk)
+
         if 'id' in self.kwargs:
             messages.success(self.request, gettext("Bulletin Board Entry updated."))
         else:
diff --git a/src/rc3platform/settings/base.py b/src/rc3platform/settings/base.py
index 85f85aab470813be34c554a51ee056d435da4ac8..4551ff24a9806cfed2e4ad261d8f31546c25782e 100644
--- a/src/rc3platform/settings/base.py
+++ b/src/rc3platform/settings/base.py
@@ -189,6 +189,9 @@ REST_FRAMEWORK = {
 # List of disallowed assembly slugs
 FORBIDDEN_ASSEMBLY_SLUGS = ['visit', 'maps', 'api', 'pusher']
 
+# list of autoban keywords. (Usage will trigger a report and send an abuse mail). Use strings or re.compile
+AUTOBAN_KEYWORDS = []
+
 # Mail configuration
 MAIL_REPLY_TO = []
 SUPPORT_HTML_MAILS = False