diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py
index d4d3509138ded063f4c1dcf5b06de07e6f5e449c..0bc456e6fc11f3f29c79da458918f705935d5c44 100644
--- a/SciPost_v1/settings/base.py
+++ b/SciPost_v1/settings/base.py
@@ -89,6 +89,7 @@ INSTALLED_APPS = (
     'commentaries',
     'comments',
     'finances',
+    'invitations',
     'journals',
     'mails',
     'mailing_lists',
diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py
index 5122b6590b62f946fcac51070dbc94fff95a3b50..2fe82bebd8ad297c4505b5fc1f5794bca5e8035d 100644
--- a/SciPost_v1/urls.py
+++ b/SciPost_v1/urls.py
@@ -36,6 +36,7 @@ urlpatterns = [
     url(r'^comments/', include('comments.urls', namespace="comments")),
     url(r'^funders/', include('funders.urls', namespace="funders")),
     url(r'^finances/', include('finances.urls', namespace="finances")),
+    url(r'^invitations/', include('invitations.urls', namespace="invitations")),
     url(r'^journals/', include('journals.urls.general', namespace="journals")),
     url(r'^mailing_list/', include('mailing_lists.urls', namespace="mailing_lists")),
     url(r'^submissions/', include('submissions.urls', namespace="submissions")),
diff --git a/scipost/templates/scipost/_assignments_summary_as_td.html b/invitations/__init__.py
similarity index 100%
rename from scipost/templates/scipost/_assignments_summary_as_td.html
rename to invitations/__init__.py
diff --git a/invitations/admin.py b/invitations/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2012f98edee74417477674d8bf2c17a310ec307
--- /dev/null
+++ b/invitations/admin.py
@@ -0,0 +1,24 @@
+from django.contrib import admin
+
+from .models import RegistrationInvitation, CitationNotification
+
+
+class RegistrationInvitationAdmin(admin.ModelAdmin):
+    date_hierarchy = 'date_sent_first'
+    search_fields = ['first_name', 'last_name', 'email', 'invitation_key']
+    list_display = ['__str__', 'invitation_type', 'invited_by', 'status']
+    list_filter = ['invitation_type', 'message_style', 'status']
+
+
+admin.site.register(RegistrationInvitation, RegistrationInvitationAdmin)
+
+
+class CitationNotificationAdmin(admin.ModelAdmin):
+    date_hierarchy = 'date_sent'
+    search_fields = ['invitation__first_name', 'invitation__last_name',
+                     'contributor__first_name', 'contributor__last_name']
+    list_display = ['__str__', 'created_by', 'date_sent', 'processed']
+    list_filter = ['processed']
+
+
+admin.site.register(CitationNotification, CitationNotificationAdmin)
diff --git a/invitations/apps.py b/invitations/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..15b9fc396a092b57fd1df51a1daeaff6afc1d176
--- /dev/null
+++ b/invitations/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class InvitationsConfig(AppConfig):
+    name = 'invitations'
diff --git a/invitations/constants.py b/invitations/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..9de09698378d2332d28ad699f7fdb06c5257be46
--- /dev/null
+++ b/invitations/constants.py
@@ -0,0 +1,24 @@
+STATUS_DRAFT, STATUS_SENT, STATUS_SENT_AND_EDITED = ('draft', 'sent', 'edited')
+STATUS_DECLINED, STATUS_REGISTERED = ('declined', 'register')
+REGISTATION_INVITATION_STATUSES = (
+    (STATUS_DRAFT, 'Draft'),
+    (STATUS_SENT, 'Sent'),
+    (STATUS_SENT_AND_EDITED, 'Sent and edited'),
+    (STATUS_DECLINED, 'Declined'),
+    (STATUS_REGISTERED, 'Registered'),
+)
+
+
+INVITATION_FORMAL, INVITATION_PERSONAL = ('F', 'P')
+INVITATION_STYLE = (
+    (INVITATION_FORMAL, 'Formal'),
+    (INVITATION_PERSONAL, 'Personal'),
+)
+
+
+INVITATION_EDITORIAL_FELLOW, INVITATION_CONTRIBUTOR, INVITATION_REFEREEING = ('F', 'C', 'R')
+INVITATION_TYPE = (
+    (INVITATION_EDITORIAL_FELLOW, 'Editorial Fellow'),
+    (INVITATION_CONTRIBUTOR, 'Contributor'),
+    (INVITATION_REFEREEING, 'Refereeing'),
+)
diff --git a/invitations/forms.py b/invitations/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..45e5fa3b5b9360981891ac511315f544f6115708
--- /dev/null
+++ b/invitations/forms.py
@@ -0,0 +1,231 @@
+from django import forms
+from django.contrib import messages
+
+from journals.models import Publication
+from scipost.models import Contributor
+from submissions.models import Submission
+
+from . import constants
+from .models import RegistrationInvitation, CitationNotification
+
+from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
+
+
+class AcceptRequestMixin:
+    def __init__(self, *args, **kwargs):
+        self.request = kwargs.pop('request')
+        super().__init__(*args, **kwargs)
+
+
+class RegistrationInvitationFilterForm(forms.Form):
+    last_name = forms.CharField()
+
+    def search(self, qs):
+        last_name = self.cleaned_data.get('last_name')
+        return qs.filter(last_name__icontains=last_name)
+
+
+class SuggestionSearchForm(forms.Form):
+    last_name = forms.CharField()
+
+    def search(self):
+        last_name = self.cleaned_data.get('last_name')
+
+        if last_name:
+            contributors = Contributor.objects.filter(user__last_name__icontains=last_name)
+            invitations = RegistrationInvitation.objects.filter(last_name__icontains=last_name)
+            declines = RegistrationInvitation.objects.declined().filter(
+                last_name__icontains=last_name)
+            return contributors, invitations, declines
+        return Contributor.objects.none(), RegistrationInvitation.objects.none()
+
+
+class CitationNotificationForm(AcceptRequestMixin, forms.ModelForm):
+    submission = AutoCompleteSelectField('submissions_lookup', required=False)
+    publication = AutoCompleteSelectField('publication_lookup', required=False)
+
+    class Meta:
+        model = CitationNotification
+        fields = (
+            'contributor',
+            'submission',
+            'publication')
+
+    def __init__(self, *args, **kwargs):
+        contributors = kwargs.pop('contributors')
+        super().__init__(*args, **kwargs)
+        if contributors:
+            self.fields['contributor'].queryset = contributors
+            self.fields['contributor'].empty_label = None
+        else:
+            self.fields['contributor'].queryset = Contributor.objects.none()
+
+    def save(self, *args, **kwargs):
+        if not hasattr(self.instance, 'created_by'):
+            self.instance.created_by = self.request.user
+        return super().save(*args, **kwargs)
+
+
+class CitationNotificationProcessForm(AcceptRequestMixin, forms.ModelForm):
+    class Meta:
+        model = CitationNotification
+        fields = ()
+
+    def get_all_notifications(self):
+        return self.instance.related_notifications().unprocessed()
+
+
+class RegistrationInvitationAddCitationForm(AcceptRequestMixin, forms.ModelForm):
+    cited_in_submissions = AutoCompleteSelectMultipleField('submissions_lookup', required=False)
+    cited_in_publications = AutoCompleteSelectMultipleField('publication_lookup', required=False)
+
+    class Meta:
+        model = RegistrationInvitation
+        fields = ()
+
+    def save(self, *args, **kwargs):
+        if kwargs.get('commit', True):
+            updated = 0
+            # Save the Submission notifications
+            submissions = Submission.objects.filter(
+                id__in=self.cleaned_data['cited_in_submissions'])
+            for submission in submissions:
+                __, _updated = CitationNotification.objects.get_or_create(
+                    invitation=self.instance,
+                    submission=submission,
+                    defaults={'created_by': self.request.user})
+                updated += 1 if _updated else 0
+
+            # Save the Publication notifications
+            publications = Publication.objects.filter(
+                id__in=self.cleaned_data['cited_in_publications'])
+            for publication in publications:
+                __, _updated = CitationNotification.objects.get_or_create(
+                    invitation=self.instance,
+                    publication=publication,
+                    defaults={'created_by': self.request.user})
+                updated += 1 if _updated else 0
+            if updated > 0:
+                self.instance.status = constants.STATUS_SENT_AND_EDITED
+                self.instance.save()
+            messages.success(self.request, '{} Citation Notification(s) added.'.format(updated))
+        return self.instance
+
+
+class RegistrationInvitationForm(AcceptRequestMixin, forms.ModelForm):
+    cited_in_submissions = AutoCompleteSelectMultipleField('submissions_lookup', required=False)
+    cited_in_publications = AutoCompleteSelectMultipleField('publication_lookup', required=False)
+
+    class Meta:
+        model = RegistrationInvitation
+        fields = (
+            'title',
+            'first_name',
+            'last_name',
+            'email',
+            'message_style',
+            'personal_message')
+
+    def __init__(self, *args, **kwargs):
+        # Find Submissions/Publications related to the invitation and fill the autocomplete fields
+        initial = kwargs.get('initial', {})
+        invitation = kwargs.get('instance', None)
+        if invitation:
+            submission_ids = invitation.citation_notifications.for_submissions().values_list(
+                'submission_id', flat=True)
+            publication_ids = invitation.citation_notifications.for_publications().values_list(
+                'publication_id', flat=True)
+            initial['cited_in_submissions'] = Submission.objects.filter(id__in=submission_ids)
+            initial['cited_in_publications'] = Publication.objects.filter(id__in=publication_ids)
+        kwargs['initial'] = initial
+        super().__init__(*args, **kwargs)
+        if not self.request.user.has_perm('scipost.can_manage_registration_invitations'):
+            del self.fields['message_style']
+            del self.fields['personal_message']
+
+
+    def clean_email(self):
+        email = self.cleaned_data['email']
+        if Contributor.objects.filter(user__email=email).exists():
+            self.add_error('email', 'This email address is already associated to a Contributor')
+        elif RegistrationInvitation.objects.declined().filter(email=email).exists():
+            self.add_error('email', 'This person has already declined an earlier invitation')
+
+        return email
+
+    def save(self, *args, **kwargs):
+        if not hasattr(self.instance, 'created_by'):
+            self.instance.created_by = self.request.user
+        invitation = super().save(*args, **kwargs)
+        if kwargs.get('commit', True):
+            # Save the Submission notifications
+            submissions = Submission.objects.filter(
+                id__in=self.cleaned_data['cited_in_submissions'])
+            for submission in submissions:
+                CitationNotification.objects.get_or_create(
+                    invitation=self.instance,
+                    submission=submission,
+                    defaults={
+                        'created_by': self.instance.created_by
+                    })
+
+            # Save the Publication notifications
+            publications = Publication.objects.filter(
+                id__in=self.cleaned_data['cited_in_publications'])
+            for publication in publications:
+                CitationNotification.objects.get_or_create(
+                    invitation=self.instance,
+                    publication=publication,
+                    defaults={
+                        'created_by': self.instance.created_by
+                    })
+        return invitation
+
+
+class RegistrationInvitationReminderForm(AcceptRequestMixin, forms.ModelForm):
+    class Meta:
+        model = RegistrationInvitation
+        fields = ()
+
+    def save(self, *args, **kwargs):
+        if kwargs.get('commit', True):
+            self.instance.mail_sent()
+        return super().save(*args, **kwargs)
+
+
+class RegistrationInvitationMapToContributorForm(AcceptRequestMixin, forms.ModelForm):
+    contributor = None
+
+    class Meta:
+        model = RegistrationInvitation
+        fields = ()
+
+    def clean(self, *args, **kwargs):
+        try:
+            self.contributor = Contributor.objects.get(
+                id=self.request.resolver_match.kwargs['contributor_id'])
+        except Contributor.DoesNotExist:
+            self.add_error(None, 'Contributor does not exist.')
+        return {}
+
+    def get_contributor(self):
+        if not self.contributor:
+            self.clean()
+        return self.contributor
+
+    def save(self, *args, **kwargs):
+        if kwargs.get('commit', True):
+            self.instance.citation_notifications.update(contributor=self.contributor)
+            self.instance.delete()
+        return self.instance
+
+
+class RegistrationInvitationMarkForm(AcceptRequestMixin, forms.ModelForm):
+    class Meta:
+        model = RegistrationInvitation
+        fields = ()
+
+    def save(self, *args, **kwargs):
+        if kwargs.get('commit', True):
+            self.instance.mail_sent()
+        return self.instance
diff --git a/invitations/managers.py b/invitations/managers.py
new file mode 100644
index 0000000000000000000000000000000000000000..84cff9b31c325853e9d784a9c2bb17de288e8565
--- /dev/null
+++ b/invitations/managers.py
@@ -0,0 +1,48 @@
+from django.db import models
+
+from . import constants
+
+
+class RegistrationInvitationQuerySet(models.QuerySet):
+    def for_fellows(self):
+        return self.filter(invitation_type=constants.INVITATION_EDITORIAL_FELLOW)
+
+    def not_for_fellows(self):
+        return self.exclude(invitation_type=constants.INVITATION_EDITORIAL_FELLOW)
+
+    def declined(self):
+        return self.filter(status=constants.STATUS_DECLINED)
+
+    def drafts(self):
+        return self.filter(status=constants.STATUS_DRAFT)
+
+    def declined_or_without_response(self):
+        return self.filter(status__in=[constants.STATUS_DECLINED,
+                                       constants.STATUS_SENT,
+                                       constants.STATUS_DRAFT,
+                                       constants.STATUS_SENT_AND_EDITED])
+
+    def sent(self):
+        return self.filter(status__in=[constants.STATUS_SENT, constants.STATUS_SENT_AND_EDITED])
+
+    def no_response(self):
+        return self.filter(status__in=[constants.STATUS_SENT,
+                                       constants.STATUS_DRAFT,
+                                       constants.STATUS_SENT_AND_EDITED])
+
+    def invited_by(self, user):
+        return self.filter(invited_by=user)
+
+
+class CitationNotificationQuerySet(models.QuerySet):
+    def for_submissions(self):
+        return self.filter(submission__isnull=False)
+
+    def for_publications(self):
+        return self.filter(publication__isnull=False)
+
+    def unprocessed(self):
+        return self.filter(processed=False)
+
+    def processed(self):
+        return self.filter(processed=False)
diff --git a/invitations/migrations/0001_initial.py b/invitations/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..4904ed6f19f62413c0dea8974ba27888b3cd1bb4
--- /dev/null
+++ b/invitations/migrations/0001_initial.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-17 12:48
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('submissions', '0008_auto_20180127_2208'),
+        ('journals', '0013_auto_20180216_0850'),
+        ('scipost', '0004_auto_20180212_1932'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='RegistrationInvitation',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(choices=[('PR', 'Prof.'), ('DR', 'Dr'), ('MR', 'Mr'), ('MRS', 'Mrs'), ('MS', 'Ms')], max_length=4)),
+                ('first_name', models.CharField(max_length=30)),
+                ('last_name', models.CharField(max_length=150)),
+                ('email', models.EmailField(max_length=254)),
+                ('status', models.CharField(choices=[('draft', 'Draft'), ('sent', 'Sent'), ('declined', 'Declined'), ('register', 'Registered')], default='draft', max_length=8)),
+                ('message_style', models.CharField(choices=[('F', 'Formal'), ('P', 'Personal')], default='F', max_length=1)),
+                ('personal_message', models.TextField(blank=True)),
+                ('invitation_type', models.CharField(choices=[('F', 'Editorial Fellow'), ('C', 'Contributor'), ('R', 'Refereeing')], default='C', max_length=2)),
+                ('invitation_key', models.CharField(max_length=40, unique=True)),
+                ('key_expires', models.DateTimeField(default=django.utils.timezone.now)),
+                ('date_sent_first', models.DateTimeField(default=django.utils.timezone.now)),
+                ('date_sent_last', models.DateTimeField(default=django.utils.timezone.now)),
+                ('number_of_reminders', models.PositiveSmallIntegerField(default=0)),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('modified', models.DateTimeField(auto_now=True)),
+                ('cited_in_publication', models.ManyToManyField(blank=True, related_name='_registrationinvitation_cited_in_publication_+', to='journals.Publication')),
+                ('cited_in_submission', models.ManyToManyField(blank=True, related_name='_registrationinvitation_cited_in_submission_+', to='submissions.Submission')),
+                ('invited_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='invitations_sent', to='scipost.Contributor')),
+            ],
+            options={
+                'ordering': ['last_name'],
+            },
+        ),
+    ]
diff --git a/invitations/migrations/0002_auto_20180217_1449.py b/invitations/migrations/0002_auto_20180217_1449.py
new file mode 100644
index 0000000000000000000000000000000000000000..1232fd833f8c4fb86ef6d42796f821e29c7a2d93
--- /dev/null
+++ b/invitations/migrations/0002_auto_20180217_1449.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-17 13:49
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('invitations', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='registrationinvitation',
+            name='date_sent_first',
+            field=models.DateTimeField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='registrationinvitation',
+            name='date_sent_last',
+            field=models.DateTimeField(blank=True, null=True),
+        ),
+    ]
diff --git a/invitations/migrations/0003_auto_20180217_1510.py b/invitations/migrations/0003_auto_20180217_1510.py
new file mode 100644
index 0000000000000000000000000000000000000000..f637c0d2a5421fd839f1ca9becc7c8ec983f27f6
--- /dev/null
+++ b/invitations/migrations/0003_auto_20180217_1510.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-17 14:10
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('invitations', '0002_auto_20180217_1449'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='registrationinvitation',
+            name='invited_by',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invitations_sent', to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/invitations/migrations/0004_registrationinvitation_created_by.py b/invitations/migrations/0004_registrationinvitation_created_by.py
new file mode 100644
index 0000000000000000000000000000000000000000..637bb72f4d55bcd33d650874707945fe48019a5e
--- /dev/null
+++ b/invitations/migrations/0004_registrationinvitation_created_by.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-17 14:51
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('invitations', '0003_auto_20180217_1510'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='registrationinvitation',
+            name='created_by',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invitations_created', to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/invitations/migrations/0005_auto_20180217_1554.py b/invitations/migrations/0005_auto_20180217_1554.py
new file mode 100644
index 0000000000000000000000000000000000000000..e88a5f2347f686379859f89835eea73bf2636d6b
--- /dev/null
+++ b/invitations/migrations/0005_auto_20180217_1554.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-17 14:54
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('invitations', '0004_registrationinvitation_created_by'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='registrationinvitation',
+            name='created_by',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitations_created', to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/invitations/migrations/0006_auto_20180217_1600.py b/invitations/migrations/0006_auto_20180217_1600.py
new file mode 100644
index 0000000000000000000000000000000000000000..d987c7dbe6de259709ae09005ebac2a38e390851
--- /dev/null
+++ b/invitations/migrations/0006_auto_20180217_1600.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-17 15:00
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('invitations', '0005_auto_20180217_1554'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='registrationinvitation',
+            old_name='number_of_reminders',
+            new_name='times_sent',
+        ),
+    ]
diff --git a/invitations/migrations/0007_auto_20180218_1200.py b/invitations/migrations/0007_auto_20180218_1200.py
new file mode 100644
index 0000000000000000000000000000000000000000..223d8fbb57806086568323a1fe6e2f72f653ddce
--- /dev/null
+++ b/invitations/migrations/0007_auto_20180218_1200.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-18 11:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('journals', '0013_auto_20180216_0850'),
+        ('submissions', '0008_auto_20180127_2208'),
+        ('invitations', '0006_auto_20180217_1600'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='registrationinvitation',
+            name='cited_in_publication',
+        ),
+        migrations.RemoveField(
+            model_name='registrationinvitation',
+            name='cited_in_submission',
+        ),
+        migrations.AddField(
+            model_name='registrationinvitation',
+            name='cited_in_publications',
+            field=models.ManyToManyField(blank=True, related_name='_registrationinvitation_cited_in_publications_+', to='journals.Publication'),
+        ),
+        migrations.AddField(
+            model_name='registrationinvitation',
+            name='cited_in_submissions',
+            field=models.ManyToManyField(blank=True, related_name='_registrationinvitation_cited_in_submissions_+', to='submissions.Submission'),
+        ),
+    ]
diff --git a/invitations/migrations/0008_auto_20180218_1305.py b/invitations/migrations/0008_auto_20180218_1305.py
new file mode 100644
index 0000000000000000000000000000000000000000..d74f26c02b7c9411230ea18f421b156c4bfb4104
--- /dev/null
+++ b/invitations/migrations/0008_auto_20180218_1305.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-18 12:05
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('submissions', '0008_auto_20180127_2208'),
+        ('journals', '0013_auto_20180216_0850'),
+        ('scipost', '0004_auto_20180212_1932'),
+        ('invitations', '0007_auto_20180218_1200'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='CitationNotification',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('processed', models.BooleanField(default=False)),
+                ('cited_in_publication', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='journals.Publication')),
+                ('cited_in_submission', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='submissions.Submission')),
+                ('contributor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='scipost.Contributor')),
+            ],
+            options={
+                'default_related_name': 'citation_notifications',
+            },
+        ),
+        migrations.RemoveField(
+            model_name='registrationinvitation',
+            name='cited_in_publications',
+        ),
+        migrations.RemoveField(
+            model_name='registrationinvitation',
+            name='cited_in_submissions',
+        ),
+        migrations.AddField(
+            model_name='citationnotification',
+            name='invitation',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='citation_notifications', to='invitations.RegistrationInvitation'),
+        ),
+    ]
diff --git a/invitations/migrations/0009_auto_20180218_1556.py b/invitations/migrations/0009_auto_20180218_1556.py
new file mode 100644
index 0000000000000000000000000000000000000000..a755f03d264593dc8191ba51b3293aa5c196dc3d
--- /dev/null
+++ b/invitations/migrations/0009_auto_20180218_1556.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-18 14:56
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0005_auto_20180218_1556'),
+        ('submissions', '0008_auto_20180127_2208'),
+        ('journals', '0013_auto_20180216_0850'),
+        ('invitations', '0008_auto_20180218_1305'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='citationnotification',
+            old_name='cited_in_publication',
+            new_name='publication',
+        ),
+        migrations.RenameField(
+            model_name='citationnotification',
+            old_name='cited_in_submission',
+            new_name='submission',
+        ),
+        migrations.AlterUniqueTogether(
+            name='citationnotification',
+            unique_together=set([('contributor', 'publication'), ('invitation', 'publication'), ('invitation', 'submission'), ('contributor', 'submission')]),
+        ),
+    ]
diff --git a/invitations/migrations/0010_auto_20180218_1613.py b/invitations/migrations/0010_auto_20180218_1613.py
new file mode 100644
index 0000000000000000000000000000000000000000..b002ed903b52c1e9422bbb2f04404376c1c2a665
--- /dev/null
+++ b/invitations/migrations/0010_auto_20180218_1613.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-18 15:13
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('invitations', '0009_auto_20180218_1556'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='citationnotification',
+            name='created',
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='citationnotification',
+            name='created_by',
+            field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, related_name='notifications_created', to=settings.AUTH_USER_MODEL),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='citationnotification',
+            name='date_sent',
+            field=models.DateTimeField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='citationnotification',
+            name='modified',
+            field=models.DateTimeField(auto_now=True),
+        ),
+        migrations.AlterField(
+            model_name='citationnotification',
+            name='invitation',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='citation_notifications', to='invitations.RegistrationInvitation'),
+        ),
+    ]
diff --git a/invitations/migrations/0011_auto_20180220_1139.py b/invitations/migrations/0011_auto_20180220_1139.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc988e8fba91f2cb20835b8e35c4482a9885f2ec
--- /dev/null
+++ b/invitations/migrations/0011_auto_20180220_1139.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-20 10:39
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+# Hack
+from django.contrib.auth import get_user_model
+
+
+def transfer_old_invitations_to_new_tables(apps, schema_editor):
+    OldRegistrationInvitation = apps.get_model('scipost', 'RegistrationInvitation')
+    OldCitationNotification = apps.get_model('scipost', 'CitationNotification')
+    NewRegistrationInvitation = apps.get_model('invitations', 'RegistrationInvitation')
+    NewCitationNotification = apps.get_model('invitations', 'CitationNotification')
+
+    random_user = get_user_model().objects.filter(is_superuser=True).first()
+    if not random_user:
+        random_user = get_user_model().objects.first()
+
+    # Registration Invitations first
+    for invitation in OldRegistrationInvitation.objects.all():
+        new_inv = NewRegistrationInvitation(
+            title=invitation.title,
+            first_name=invitation.first_name,
+            last_name=invitation.last_name,
+            email=invitation.email,
+            invitation_type=invitation.invitation_type,
+            created_by_id=invitation.invited_by.user.id if invitation.invited_by else random_user.id,
+            invited_by_id=invitation.invited_by.user.id if invitation.invited_by else None,
+            message_style=invitation.message_style,
+            personal_message=invitation.personal_message,
+            times_sent=invitation.nr_reminders + 1,
+            date_sent_first=invitation.date_sent,
+            date_sent_last=invitation.date_last_reminded,
+            created=invitation.date_sent,
+            modified=invitation.date_sent,
+            key_expires=invitation.key_expires,
+            invitation_key=invitation.invitation_key,
+        )
+        if new_inv.invitation_type in ['ci', 'cp']:
+            new_inv.invitation_type = 'C'
+
+        if not invitation.responded:
+            new_inv.status = 'sent'
+        elif invitation.declined:
+            new_inv.status = 'declined'
+        elif invitation.responded and not invitation.declined:
+            new_inv.status = 'register'
+        else:
+            new_inv.status = 'draft'
+        new_inv.save()
+
+        if invitation.cited_in_submission:
+            NewCitationNotification.objects.create(
+                invitation_id=new_inv.id,
+                created_by_id=invitation.invited_by.user.id if invitation.invited_by else random_user.id,
+                created=new_inv.created,
+                modified=new_inv.modified,
+                submission_id=invitation.cited_in_submission.id,
+                date_sent=invitation.date_sent_first,
+                processed=(new_inv.status in ['declined', 'register', 'sent']),
+            )
+        if invitation.cited_in_publication:
+            NewCitationNotification.objects.create(
+                invitation_id=new_inv.id,
+                created_by_id=invitation.invited_by.user.id if invitation.invited_by else random_user.id,
+                created=new_inv.created,
+                modified=new_inv.modified,
+                publication_id=invitation.cited_in_publication.id,
+                date_sent=invitation.date_sent_first,
+                processed=(new_inv.status in ['declined', 'register', 'sent']),
+            )
+
+    # Old CitationNotifications
+    for notification in OldCitationNotification.objects.all():
+        NewCitationNotification.objects.create(
+            contributor_id=notification.contributor.id if notification.contributor else None,
+            created_by_id=random_user.id,
+            submission_id=notification.cited_in_submission.id if notification.cited_in_submission else None,
+            publication_id=notification.cited_in_publication.id if notification.cited_in_publication else None,
+            processed=notification.processed,
+        )
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('invitations', '0010_auto_20180218_1613'),
+    ]
+
+    operations = [
+        migrations.RunPython(transfer_old_invitations_to_new_tables),
+    ]
diff --git a/invitations/migrations/__init__.py b/invitations/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/invitations/mixins.py b/invitations/mixins.py
new file mode 100644
index 0000000000000000000000000000000000000000..8635746bd3746735a6aad2e61a6236f525557f80
--- /dev/null
+++ b/invitations/mixins.py
@@ -0,0 +1,48 @@
+from django.db import transaction
+from django.contrib import messages
+from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
+
+
+class RequestArgumentMixin:
+    """
+    Use the WSGIRequest as an argument in the form.
+    """
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+        kwargs['request'] = self.request
+        return kwargs
+
+
+class PermissionsMixin(LoginRequiredMixin, PermissionRequiredMixin):
+    pass
+
+
+class SaveAndSendFormMixin:
+    """
+    Use the Save or Save and Send option to send the mail out after form is valid.
+    """
+    send_mail = None
+
+    def post(self, request, *args, **kwargs):
+        # Intercept the specific submit value before validation the form so `MailEditorMixin`
+        # can use this data.
+        if self.send_mail is None:
+            self.send_mail = request.POST.get('save', '') == 'save_and_send'
+            if self.send_mail:
+                self.send_mail = request.user.has_perm('scipost.can_manage_registration_invitations')
+
+        # Communicate with the `MailEditorMixin` whether the mails should go out or not.
+        self.has_permission_to_send_mail = self.send_mail
+        return super().post(request, *args, **kwargs)
+
+    @transaction.atomic
+    def form_valid(self, form):
+        # Communication with the user.
+        model_name = self.object._meta.verbose_name
+        model_name = model_name[:1].upper() + model_name[1:]  # Hack it to capitalize the name
+        if self.send_mail:
+            self.object.mail_sent()
+            messages.success(self.request, '{} updated and sent'.format(model_name))
+        else:
+            messages.success(self.request, '{} updated'.format(model_name))
+        return super().form_valid(form)
diff --git a/invitations/models.py b/invitations/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..aeac667d9229d512250b43b897fa1ec10a5ae2d2
--- /dev/null
+++ b/invitations/models.py
@@ -0,0 +1,188 @@
+import datetime
+import hashlib
+import random
+import string
+
+from django.db import models, IntegrityError
+from django.conf import settings
+from django.utils import timezone
+
+from . import constants
+from .managers import RegistrationInvitationQuerySet, CitationNotificationQuerySet
+
+from scipost.constants import TITLE_CHOICES
+
+
+class RegistrationInvitation(models.Model):
+    """
+    Invitation to particular persons for registration
+    """
+    title = models.CharField(max_length=4, choices=TITLE_CHOICES)
+    first_name = models.CharField(max_length=30)
+    last_name = models.CharField(max_length=150)
+    email = models.EmailField()
+    status = models.CharField(max_length=8, choices=constants.REGISTATION_INVITATION_STATUSES,
+                              default=constants.STATUS_DRAFT)
+
+    # Text content
+    message_style = models.CharField(max_length=1, choices=constants.INVITATION_STYLE,
+                                     default=constants.INVITATION_FORMAL)
+    personal_message = models.TextField(blank=True)
+    invited_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
+                                   blank=True, null=True, related_name='invitations_sent')
+    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='invitations_created')
+
+    # Related to objects
+    invitation_type = models.CharField(max_length=2, choices=constants.INVITATION_TYPE,
+                                       default=constants.INVITATION_CONTRIBUTOR)
+
+    # Response keys
+    invitation_key = models.CharField(max_length=40, unique=True)
+    key_expires = models.DateTimeField(default=timezone.now)
+
+    # Timestamps
+    date_sent_first = models.DateTimeField(null=True, blank=True)
+    date_sent_last = models.DateTimeField(null=True, blank=True)
+    times_sent = models.PositiveSmallIntegerField(default=0)
+    created = models.DateTimeField(auto_now_add=True)
+    modified = models.DateTimeField(auto_now=True)
+
+    objects = RegistrationInvitationQuerySet.as_manager()
+
+    class Meta:
+        ordering = ['last_name']
+
+    def __str__(self):
+        return '{} {} on {}'.format(self.first_name, self.last_name,
+                                    self.created.strftime("%Y-%m-%d"))
+
+    def save(self, *args, **kwargs):
+        self.refresh_keys(commit=False)
+        return super().save(*args, **kwargs)
+
+    def refresh_keys(self, force_new_key=False, commit=True):
+        # Generate email activation key and link
+        if not self.invitation_key or force_new_key:
+            # TODO: Replace this all by the `secrets` package available from python 3.6(!)
+            salt = ''
+            for i in range(5):
+                salt += random.choice(string.ascii_letters)
+            salt = salt.encode('utf8')
+            invitationsalt = self.last_name.encode('utf8')
+            self.invitation_key = hashlib.sha1(salt + invitationsalt).hexdigest()
+            self.key_expires = timezone.now() + datetime.timedelta(days=365)
+        if commit:
+            self.save()
+
+    def mail_sent(self, user=None):
+        """
+        Update instance fields as if a new invitation mail has been sent out.
+        """
+        if self.status == constants.STATUS_DRAFT:
+            self.status = constants.STATUS_SENT
+        if not self.date_sent_first:
+            self.date_sent_first = timezone.now()
+        self.date_sent_last = timezone.now()
+        self.invited_by = user or self.created_by
+        self.times_sent += 1
+        self.citation_notifications.update(processed=True)
+        self.save()
+
+    @property
+    def has_responded(self):
+        return self.status in [constants.STATUS_DECLINED, constants.STATUS_REGISTERED]
+
+
+class CitationNotification(models.Model):
+    invitation = models.ForeignKey('invitations.RegistrationInvitation',
+                                   on_delete=models.SET_NULL,
+                                   null=True, blank=True)
+    contributor = models.ForeignKey('scipost.Contributor',
+                                    on_delete=models.CASCADE,
+                                    null=True, blank=True,
+                                    related_name='+')
+
+    # Content
+    submission = models.ForeignKey('submissions.Submission', null=True, blank=True,
+                                   related_name='+')
+    publication = models.ForeignKey('journals.Publication', null=True, blank=True,
+                                    related_name='+')
+    processed = models.BooleanField(default=False)
+
+    # Meta info
+    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notifications_created')
+    date_sent = models.DateTimeField(null=True, blank=True)
+    created = models.DateTimeField(auto_now_add=True)
+    modified = models.DateTimeField(auto_now=True)
+
+    objects = CitationNotificationQuerySet.as_manager()
+
+    class Meta:
+        default_related_name = 'citation_notifications'
+        unique_together = (
+            ('invitation', 'submission'),
+            ('invitation', 'publication'),
+            ('contributor', 'submission'),
+            ('contributor', 'publication'),
+        )
+
+    def __str__(self):
+        _str = 'Citation for '
+        if self.invitation:
+            _str += ' Invitation ({} {})'.format(
+                self.invitation.first_name,
+                self.invitation.last_name,
+            )
+        elif self.contributor:
+            _str += ' Contributor ({})'.format(self.contributor)
+
+        _str += ' on '
+        if self.submission:
+            _str += 'Submission ({})'.format(self.submission.arxiv_identifier_w_vn_nr)
+        elif self.publication:
+            _str += 'Publication ({})'.format(self.publication.doi_label)
+        return _str
+
+    def save(self, *args, **kwargs):
+        if not self.submission and not self.publication:
+            raise IntegrityError(('CitationNotification needs to be related to either a '
+                                  'Submission or Publication object.'))
+        return super().save(*args, **kwargs)
+
+    def mail_sent(self):
+        """
+        Update instance fields as if a new citation notification mail has been sent out.
+        """
+        self.processed = True
+        if not self.date_sent:
+            # Don't overwrite by accident...
+            self.date_sent = timezone.now()
+        self.save()
+
+    def related_notifications(self):
+        return CitationNotification.objects.unprocessed().filter(
+            models.Q(contributor=self.contributor) | models.Q(invitation=self.invitation))
+
+    def get_first_related_contributor(self):
+        return self.related_notifications().filter(contributor__isnull=False).first()
+
+    @property
+    def email(self):
+        if self.invitation:
+            return self.invitation.email
+        elif self.contributor:
+            return self.contributor.user.email
+
+    @property
+    def last_name(self):
+        if self.invitation:
+            return self.invitation.last_name
+        elif self.contributor:
+            return self.contributor.last_name
+
+    @property
+    def get_title(self):
+        if self.invitation:
+            return self.invitation.get_title_display()
+        elif self.contributor:
+            return self.contributor.get_title_display()
diff --git a/invitations/templates/invitations/citationnotification_form.html b/invitations/templates/invitations/citationnotification_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..49d8c3eed57363ad33725729142db774d0a808fe
--- /dev/null
+++ b/invitations/templates/invitations/citationnotification_form.html
@@ -0,0 +1,39 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% block pagetitle %}: Process Citation Notification{% endblock pagetitle %}
+
+{% load bootstrap %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
+    <a href="{% url 'invitations:citation_notification_list' %}" class="breadcrumb-item">Citation Notifications</a>
+    <span class="breadcrumb-item">Process</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Process Citation Notification</h1>
+
+        <h3>All related unprocessed Citation Notifications</h3>
+        {% for related_notification in form.get_all_notifications %}
+            {% include 'partials/invitations/citationnotification_summary.html' with notification=related_notification %}
+            <br>
+        {% endfor %}
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+        <form method="post">
+            {% csrf_token %}
+            {{ form|bootstrap }}
+            <input type="submit" class="btn btn-primary" name="save" value="Process all notifications">
+        </form>
+
+    </div>
+</div>
+
+{% endblock %}
diff --git a/invitations/templates/invitations/citationnotification_list.html b/invitations/templates/invitations/citationnotification_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..c24a4abb6a3851fd9883ff60f8592c481a1d8da6
--- /dev/null
+++ b/invitations/templates/invitations/citationnotification_list.html
@@ -0,0 +1,25 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: Unprocessed Citation Notifications{% endblock pagetitle %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
+    <span class="breadcrumb-item">Unprocessed Citation Notifications</span>
+{% endblock %}
+
+{% block content %}
+
+<h1 class="highlight">Unprocessed Citation Notifications</h1>
+<a href="{% url 'invitations:list' %}">Back to Registration Invitations</a>
+
+<div class="row">
+    <div class="col-12">
+        <br>
+        {% include 'partials/invitations/citationnotification_table.html' with notifications=object_list %}
+    </div>
+</div>
+
+{% endblock %}
diff --git a/scipost/templates/scipost/registration_invitations_cleanup.html b/invitations/templates/invitations/registrationinvitation_cleanup.html
similarity index 57%
rename from scipost/templates/scipost/registration_invitations_cleanup.html
rename to invitations/templates/invitations/registrationinvitation_cleanup.html
index 539c00d807dae937ee87b2376f359458b5a9c72f..d4a9620d11b2da9a4a47b459c02150028362260c 100644
--- a/scipost/templates/scipost/registration_invitations_cleanup.html
+++ b/invitations/templates/invitations/registrationinvitation_cleanup.html
@@ -1,11 +1,11 @@
 {% extends 'scipost/_personal_page_base.html' %}
 
-{% block pagetitle %}: registration invitations cleanup{% endblock pagetitle %}
+{% block pagetitle %}: Registration Invitations cleanup{% endblock pagetitle %}
 
 
 {% block breadcrumb_items %}
-    {{block.super}}
-    <a href="{% url 'scipost:registration_invitations' %}" class="breadcrumb-item">Registration invitations</a>
+    {{ block.super }}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
     <span class="breadcrumb-item">Cleanup</span>
 {% endblock %}
 
@@ -15,13 +15,13 @@
 <div class="row">
     <div class="col-12">
         <h1 class="highlight">Registration Invitations Cleanup</h1>
+        <h3>Email duplicates (a contributor exists with the email address in these invitations)</h3>
     </div>
 </div>
 
 
 <div class="row">
     <div class="col-12">
-        <h3>Email duplicates (a contributor exists with the email address in these invitations)</h3>
         <table class="table">
             <thead>
                 <tr>
@@ -34,29 +34,23 @@
                 </tr>
             </thead>
             <tbody>
-                {% for inv in invs_to_cleanup %}
+                {% for inv in invitations %}
                     <tr>
                         <td>{{ inv.last_name }}</td>
                         <td>{{ inv.first_name }}</td>
                         <td>{{ inv.email }}</td>
-                        <td>{{ inv.date_sent }} </td>
-                        <td>{{ inv.invitation_type }}</td>
-                        <td>{{ inv.invited_by.user.last_name }}</td>
-                        <td>
-                            <a href="{% url 'scipost:remove_registration_invitation' invitation_id=inv.id %}">Remove</a>
-                        </td>
+                        <td>{{ inv.date_sent_first }} </td>
+                        <td>{{ inv.get_invitation_type_display }}</td>
+                        <td>{{ inv.invited_by }}</td>
+                        <td><a href="{% url 'invitations:delete' inv.id %}">Remove</a></td>
                     </tr>
                 {% empty %}
                     <tr>
-                        <td colspan="7">
-                            There were no duplicate emails found in the sets of Contributors/Invitations.
-                        </td>
+                        <td colspan="7">There were no duplicate emails found in the sets of Contributors/Invitations.</td>
                     </tr>
                 {% endfor %}
             </tbody>
         </table>
-
-        <p>Return to the <a href="{% url 'scipost:registration_invitations' %}">Registration Invitations</a> page.</p>
     </div>
 </div>
 
diff --git a/invitations/templates/invitations/registrationinvitation_confirm_delete.html b/invitations/templates/invitations/registrationinvitation_confirm_delete.html
new file mode 100644
index 0000000000000000000000000000000000000000..c65958c8547340e2b5dc9f79d564c029eb5e5166
--- /dev/null
+++ b/invitations/templates/invitations/registrationinvitation_confirm_delete.html
@@ -0,0 +1,27 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% block pagetitle %}: Delete Registration Invitation{% endblock pagetitle %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
+    <span class="breadcrumb-item">Delete {{ object.id }}</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Delete Registration Invitation {{ object.id }}</h1>
+        <p>Are you sure you want to delete the Registration Invitation?</p>
+        {% include 'partials/invitations/registrationinvitation_summary.html' with invitation=object %}
+
+        <form method="post">
+            {% csrf_token %}
+            <input type="submit" class="btn btn-danger" value="Delete Registration Invitation">
+        </form>
+
+    </div>
+</div>
+
+{% endblock %}
diff --git a/invitations/templates/invitations/registrationinvitation_form.html b/invitations/templates/invitations/registrationinvitation_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..878ad6087d05b52a539f6ff611d3958c1d5b61b5
--- /dev/null
+++ b/invitations/templates/invitations/registrationinvitation_form.html
@@ -0,0 +1,37 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% block pagetitle %}: Edit Registration Invitation{% endblock pagetitle %}
+
+{% load scipost_extras %}
+{% load bootstrap %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
+    <span class="breadcrumb-item">Edit</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Registration Invitation {{ object.id }}</h1>
+
+        <form method="post">
+            {% csrf_token %}
+            {{ form|bootstrap }}
+            <button type="submit" class="btn btn-primary" name="save" value="save">Save</button>
+            <button type="submit" class="ml-2 btn btn-primary" name="save" value="save_and_create">Save and create new</button>
+            {% if perms.scipost.can_manage_registration_invitations %}
+                <button type="submit" class="ml-2 btn btn-secondary" name="save" value="save_and_send">Save and send mail</button>
+            {% endif %}
+        </form>
+    </div>
+</div>
+
+{% endblock %}
+
+{% block footer_script %}
+    {{ block.super }}
+    {{ form.media }}
+{% endblock footer_script %}
diff --git a/invitations/templates/invitations/registrationinvitation_form_add_citation.html b/invitations/templates/invitations/registrationinvitation_form_add_citation.html
new file mode 100644
index 0000000000000000000000000000000000000000..af960d23a5b97ac8823e1b3d2b7b169d71296ccb
--- /dev/null
+++ b/invitations/templates/invitations/registrationinvitation_form_add_citation.html
@@ -0,0 +1,43 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% block pagetitle %}: Add Citation Notification{% endblock pagetitle %}
+
+{% load bootstrap %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
+    <span class="breadcrumb-item">Add Citation Notification</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Add Citation Notification</h1>
+        <h3>Registration Invitation</h3>
+        {% include 'partials/invitations/registrationinvitation_summary.html' with invitation=object %}
+    </div>
+</div>
+
+<hr class="divider">
+
+<div class="row">
+    <div class="col-12">
+        <h3>Submission or Publication to add to the Registration Invitation</h3>
+        <br>
+        <form method="post">
+            {% csrf_token %}
+            {{ form|bootstrap }}
+            <input type="submit" class="btn btn-primary" value="Add">
+        </form>
+
+    </div>
+</div>
+
+{% endblock %}
+
+{% block footer_script %}
+    {{ block.super }}
+    {{ form.media }}
+{% endblock footer_script %}
diff --git a/invitations/templates/invitations/registrationinvitation_form_add_new.html b/invitations/templates/invitations/registrationinvitation_form_add_new.html
new file mode 100644
index 0000000000000000000000000000000000000000..922c6f743be867698e6c4cb2513ae40ef85300f8
--- /dev/null
+++ b/invitations/templates/invitations/registrationinvitation_form_add_new.html
@@ -0,0 +1,100 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% block pagetitle %}: New Registration Invitation{% endblock pagetitle %}
+
+{% load scipost_extras %}
+{% load bootstrap %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
+    <span class="breadcrumb-item">New</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">New Registration Invitation</h1>
+        <p>
+            If you want to invite a new Contributor to SciPost, first try to use the following search form to see if this person already is available in the SciPost database.
+        </p>
+
+        {% if suggestion_search_form %}
+            <h3 class="mb-1">Search for existing Contributor</h3>
+            <form method="get">
+                {{ suggestion_search_form|bootstrap }}
+                <input type="submit" class="btn btn-primary" value="Search">
+                {% if suggestion_search_form.is_bound %}
+                    <a href="{% url 'invitations:new' %}" class="ml-2 btn btn-link">Cancel search</a>
+                {% endif %}
+            </form>
+
+            <hr class="divider">
+            {% if suggestion_search_form.is_bound %}
+                {% if suggested_invitations %}
+                    <h3>Registration Invitations found</h3>
+                    <p>
+                        If the person you are trying to invite is within this list of Registration Invitations, please use it by extending that particular invitation.
+                    </p>
+                    <ul class="mb-2">
+                        {% for inv in suggested_invitations %}
+                            <li><a href="{% url 'invitations:add_citation' inv.id %}">Use Registration Invitation for {{ inv.first_name }} {{ inv.last_name }}</a></li>
+                        {% endfor %}
+                    </ul>
+                {% endif %}
+                {% if declined_invitations %}
+                    <h3>Declined Registration Invitations</h3>
+                    <p>
+                        If the person you are trying to invite is within this list of Registration Invitations, do not invite them again. They have already declined an earlier invitation.
+                    </p>
+                    <ul class="mb-2">
+                        {% for inv in declined_invitations %}
+                            <li>{{ inv.first_name }} {{ inv.last_name }}: {{ inv.email }}</a></li>
+                        {% endfor %}
+                    </ul>
+                {% endif %}
+
+                <h3>Citation Notification</h3>
+                <p>
+                    If the person you are trying to invite is already a registered Contributor, it'll be listed in the following form. If not, you can <a href="{% url 'invitations:new' %}?prefill-last_name={{ suggestion_search_form.last_name.value|urlencode }}">write a new Registration Invitation</a>.
+                </p>
+            {% else %}
+                <h3 class="mb-1">...or write a new Registration Invitation</h3>
+            {% endif %}
+
+        {% endif %}
+
+        {% if suggestion_search_form.is_bound %}
+            <form method="post">
+                {% csrf_token %}
+                {{ citation_form|bootstrap }}
+                <button type="submit" class="btn btn-primary" name="save" value="save">Save</button>
+                <button type="submit" class="ml-2 btn btn-primary" name="save" value="save_and_create">Save and create new</button>
+                {% if perms.scipost.can_manage_registration_invitations %}
+                    <button type="submit" class="ml-2 btn btn-secondary" name="save" value="save_and_send">Save and send mail</button>
+                {% endif %}
+            </form>
+
+            <br>
+            <a href="{% url 'invitations:new' %}">Cancel search here</a> to write a new Registration Invitation.
+        {% else %}
+            <form method="post">
+                {% csrf_token %}
+                {{ invitation_form|bootstrap }}
+                <button type="submit" class="btn btn-primary" name="save" value="save">Save</button>
+                <button type="submit" class="ml-2 btn btn-primary" name="save" value="save_and_create">Save and create new</button>
+                {% if perms.scipost.can_manage_registration_invitations %}
+                    <button type="submit" class="ml-2 btn btn-secondary" name="save" value="save_and_send">Save and send mail</button>
+                {% endif %}
+            </form>
+        {% endif %}
+    </div>
+</div>
+
+{% endblock %}
+
+{% block footer_script %}
+    {{ block.super }}
+    {{ invitation_form.media }}
+{% endblock footer_script %}
diff --git a/invitations/templates/invitations/registrationinvitation_form_map_to_contributor.html b/invitations/templates/invitations/registrationinvitation_form_map_to_contributor.html
new file mode 100644
index 0000000000000000000000000000000000000000..46b8e5fa11019753e665d7c263a9d52b0ce52824
--- /dev/null
+++ b/invitations/templates/invitations/registrationinvitation_form_map_to_contributor.html
@@ -0,0 +1,38 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% block pagetitle %}: Map Registration Invitation{% endblock pagetitle %}
+
+{% load bootstrap %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
+    <span class="breadcrumb-item">Map to Contributor</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Map Registration Invitation to Contributor</h1>
+        <h3>Registration Invitation</h3>
+        {% include 'partials/invitations/registrationinvitation_summary.html' with invitation=object %}
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+        <form method="post">
+            {% csrf_token %}
+            {{ form|bootstrap }}
+            <h3>Map to Contributor</h3>
+            {{ form.get_contributor.get_title_display }} {{ form.get_contributor.user.first_name }} {{ form.get_contributor.user.last_name }}
+            <br>
+            <br>
+            <input type="submit" class="btn btn-primary" name="save" value="Map to Contributor">
+        </form>
+
+    </div>
+</div>
+
+{% endblock %}
diff --git a/invitations/templates/invitations/registrationinvitation_form_mark_as.html b/invitations/templates/invitations/registrationinvitation_form_mark_as.html
new file mode 100644
index 0000000000000000000000000000000000000000..e288ceadd2fa9298764caa9efba8bf38a90421bf
--- /dev/null
+++ b/invitations/templates/invitations/registrationinvitation_form_mark_as.html
@@ -0,0 +1,33 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% block pagetitle %}: Mark Registration Invitation {{ request.resolver_match.kwargs.label }}{% endblock pagetitle %}
+
+{% load bootstrap %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
+    <span class="breadcrumb-item">Mark {{ request.resolver_match.kwargs.label }}</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Mark Registration Invitation {{ request.resolver_match.kwargs.label }}</h1>
+        {% include 'partials/invitations/registrationinvitation_summary.html' with invitation=object %}
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+        <form method="post">
+            {% csrf_token %}
+            {{ form|bootstrap }}
+            <button type="submit" class="btn btn-primary" name="save" value="save">Mark {{ request.resolver_match.kwargs.label }}</button>
+        </form>
+
+    </div>
+</div>
+
+{% endblock %}
diff --git a/invitations/templates/invitations/registrationinvitation_list.html b/invitations/templates/invitations/registrationinvitation_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..9ef7afeeaa17c0fda2f5ba2d806ae1866e807c97
--- /dev/null
+++ b/invitations/templates/invitations/registrationinvitation_list.html
@@ -0,0 +1,61 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: Registration Invitations{% endblock pagetitle %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <span class="breadcrumb-item">Registration Invitations</span>
+{% endblock %}
+
+{% block content %}
+
+<h1 class="highlight">Registration Invitations</h1>
+
+<div class="row">
+    <div class="col-md-6">
+        <h3>Actions</h3>
+        <ul class="mb-0">
+            {% if perms.scipost.can_create_registration_invitations %}
+                <li><a href="{% url 'invitations:new' %}">Create a new invitation</a></li>
+            {% endif %}
+            {% if perms.scipost.can_manage_registration_invitations %}
+                <li><a href="{% url 'invitations:cleanup' %}">Perform a cleanup</a></li>
+                <li><a href="{% url 'invitations:citation_notification_list' %}">List unprocessed Citation Notifications</a></li>
+            {% endif %}
+        </ul>
+    </div>
+    <div class="col-md-6 text-md-right">
+        <h3>Quick stats</h3>
+        Number in draft: {{ count_in_draft }}<br>
+        Number pending response: {{ count_pending }}
+
+        {% if perms.scipost.can_create_registration_invitations %}
+            <br><br>
+            <a href="{% url 'invitations:new' %}" class="btn btn-primary">Create a new invitation</a>
+        {% endif %}
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+        <h2 class="highlight">Registration Invitations</h2>
+    </div>
+    <div class="col-md-6">
+        <form method="get">
+            {{ search_form|bootstrap }}
+            <input class="btn btn-primary" type="submit" value="Filter">
+            <a href="{% url 'invitations:list' %}" class="btn btn-link">Reset filter</a>
+        </form>
+    </div>
+    <div class="col-12">
+        <br>
+        {% include 'partials/invitations/registrationinvitation_table.html' with invitations=object_list %}
+        {% if search_form.is_bound %}
+            <a href="{% url 'invitations:list' %}" class="btn btn-link">Reset filter</a>
+        {% endif %}
+    </div>
+</div>
+
+{% endblock %}
diff --git a/invitations/templates/invitations/registrationinvitation_reminder_form.html b/invitations/templates/invitations/registrationinvitation_reminder_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..c328668dc3dc632039fbaa7ddca64a6cbb37920f
--- /dev/null
+++ b/invitations/templates/invitations/registrationinvitation_reminder_form.html
@@ -0,0 +1,34 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% block pagetitle %}: Send reminder for Registration Invitation{% endblock pagetitle %}
+
+{% load scipost_extras %}
+{% load bootstrap %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{% url 'invitations:list' %}" class="breadcrumb-item">Registration Invitations</a>
+    <span class="breadcrumb-item">Send reminder</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Send reminder for Registration Invitation</h1>
+        {% include 'partials/invitations/registrationinvitation_summary.html' with invitation=object %}
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+        <form method="post">
+            {% csrf_token %}
+            {{ form|bootstrap }}
+            <input type="submit" class="btn btn-primary" value="Send reminder">
+        </form>
+
+    </div>
+</div>
+
+{% endblock %}
diff --git a/invitations/templates/partials/invitations/citationnotification_summary.html b/invitations/templates/partials/invitations/citationnotification_summary.html
new file mode 100644
index 0000000000000000000000000000000000000000..9bf9479b994638c448280a61a05ec630df415f37
--- /dev/null
+++ b/invitations/templates/partials/invitations/citationnotification_summary.html
@@ -0,0 +1,42 @@
+<table class="registration_invitation notification">
+    <tr>
+        <th>Name</th>
+        <td>
+            {% if notification.contributor %}
+                {{ notification.contributor.get_title_display }} {{ notification.contributor.user.first_name }} {{ notification.contributor.user.last_name }}
+            {% elif notification.invitation %}
+                {{ notification.invitation.first_name }} {{ notification.invitation.last_name }}
+            {% endif %}
+        </td>
+    </tr>
+    <tr>
+        <th>Email</th>
+        <td>
+            {% if notification.contributor %}
+                {{ notification.contributor.user.email }}
+            {% elif notification.invitation %}
+                {{ notification.invitation.email }}
+            {% endif %}
+        </td>
+    </tr>
+    <tr>
+        <th>Status</th>
+        <td>{{ notification.processed|yesno:'Processed,Not processed' }}</td>
+    </tr>
+    <tr>
+        <th>Submission</th>
+        <td>{{ notification.submission|default:'-' }}</td>
+    </tr>
+    <tr>
+        <th>Publication</th>
+        <td>{{ notification.publication|default:'-' }}</td>
+    </tr>
+    <tr>
+        <th>Created</th>
+        <td>{{ notification.created }} (by {{ notification.created_by.first_name }} {{ notification.created_by.last_name }})</td>
+    </tr>
+    <tr>
+        <th>Date Sent</th>
+        <td>{{ notification.date_sent|default:'<em>Not sent yet</em>' }}</td>
+    </tr>
+</table>
diff --git a/invitations/templates/partials/invitations/citationnotification_table.html b/invitations/templates/partials/invitations/citationnotification_table.html
new file mode 100644
index 0000000000000000000000000000000000000000..7ec0ed3b93c9dba68c585b86f630ccfc8b294c96
--- /dev/null
+++ b/invitations/templates/partials/invitations/citationnotification_table.html
@@ -0,0 +1,63 @@
+<table class="table">
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Email</th>
+            <th>Type</th>
+            <th>Cited in</th>
+            <th>Created by</th>
+            <th>Date created</th>
+            <th>Actions</th>
+        </tr>
+    </thead>
+    <tbody>
+        {% for notification in notifications %}
+              <tr>
+                  <td>
+                      {% if notification.contributor %}
+                          {{ notification.contributor.user.first_name }} {{ notification.contributor.user.last_name }}
+                      {% elif notification.invitation %}
+                          {{ notification.invitation.first_name }} {{ notification.invitation.last_name }}
+                      {% endif %}
+                  </td>
+                  <td>
+                      {% if notification.contributor %}
+                          {{ notification.contributor.user.email }}
+                      {% elif notification.invitation %}
+                          {{ notification.invitation.email }}
+                      {% endif %}
+                  </td>
+                  <td>
+                      {% if notification.contributor %}For Contributor{% elif notification.invitation %}Registration Invitation{% else %}<span class="text-danger">Invalid</span>{% endif %}
+                  </td>
+                  <td>
+                      {% if notification.publication %}
+                        {{ notification.publication.citation }}
+                      {% endif %}
+                      {% if notification.submission %}
+                        {{ notification.submission.arxiv_identifier_w_vn_nr }}
+                      {% endif %}
+                  </td>
+                <td>{{ notification.created_by.first_name }} {{ notification.created_by.last_name }}</td>
+                <td>{{ notification.created }}</td>
+                <td>
+                    {% if notification.contributor %}
+                        <a href="{% url 'invitations:citation_notification_process' notification.id %}">Process citation</a>
+                    {% elif notification.invitation %}
+                        {% if notification.invitation.status == 'draft' %}
+                            <a href="{% url 'invitations:update' notification.invitation.id %}">Edit/send invitation</a>
+                        {% elif notification.invitation.status == 'send' or notification.invitation.status == 'edited' %}
+                            <a href="{% url 'invitations:send_reminder' notification.invitation.id %}">Send reminder</a>
+                        {% endif %}
+                    {% endif %}
+                </td>
+            </tr>
+        {% empty %}
+            <tr>
+                <td colspan="7">
+                    All Citation Notifications have been processed.
+                </td>
+            </tr>
+        {% endfor %}
+    </tbody>
+</table>
diff --git a/invitations/templates/partials/invitations/registrationinvitation_summary.html b/invitations/templates/partials/invitations/registrationinvitation_summary.html
new file mode 100644
index 0000000000000000000000000000000000000000..4ff47a7f113268c1816d49986cccc5bf33fe0ac6
--- /dev/null
+++ b/invitations/templates/partials/invitations/registrationinvitation_summary.html
@@ -0,0 +1,53 @@
+<table class="registration_invitation">
+    <tr>
+        <th>Name</th>
+        <td>{{ invitation.get_title_display }} {{ invitation.first_name }} {{ invitation.last_name }}</td>
+    </tr>
+    <tr>
+        <th>Email</th>
+        <td>{{ invitation.email }}</td>
+    </tr>
+    <tr>
+        <th>Status</th>
+        <td>{{ invitation.get_status_display }}</td>
+    </tr>
+
+    <tr>
+        <th>Submission(s)</th>
+        <td>
+            <ul class="pl-3 mb-0">
+                {% for citation in invitation.citation_notifications.for_submissions %}
+                    <li>{{ citation.submission }}</li>
+                {% empty %}
+                    <li><em>No submissions linked</em></li>
+                {% endfor %}
+            </ul>
+        </td>
+    </tr>
+    <tr>
+        <th>Publication(s)</th>
+        <td>
+            <ul class="pl-3 mb-0">
+                {% for citation in invitation.citation_notifications.for_publications %}
+                    <li>{{ citation.publication }}</li>
+                {% empty %}
+                    <li><em>No publications linked</em></li>
+                {% endfor %}
+            </ul>
+        </td>
+    </tr>
+    <tr>
+        <th>Created</th>
+        <td>{{ invitation.created }}</td>
+    </tr>
+    <tr>
+        <th>Times sent</th>
+        <td><strong>{{ invitation.times_sent }}</strong> time{{ invitation.times_sent|pluralize }}{% if invitation.times_sent %} (last time: {{ invitation.date_sent_last }}){% endif %}</td>
+    </tr>
+</table>
+
+{% if invitation.personal_message %}
+    <br>
+    <strong>Personal Message</strong>
+    <p>{{ invitation.personal_message|linebreaksbr }}</p>
+{% endif %}
diff --git a/invitations/templates/partials/invitations/registrationinvitation_table.html b/invitations/templates/partials/invitations/registrationinvitation_table.html
new file mode 100644
index 0000000000000000000000000000000000000000..8bd8eac5c87b49e4378e5234d4f46505e5ea66aa
--- /dev/null
+++ b/invitations/templates/partials/invitations/registrationinvitation_table.html
@@ -0,0 +1,71 @@
+{% load scipost_extras %}
+
+<table class="table">
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Email</th>
+            <th>Status</th>
+            <th>Type</th>
+            <th>Drafted by</th>
+            <th>Date created</th>
+            <th>Times sent</th>
+            <th colspan="2">Actions</th>
+        </tr>
+    </thead>
+    <tbody>
+        {% for invitation in invitations %}
+          <tr>
+            <td>{{ invitation.last_name }}, {{ invitation.first_name }}</td>
+            <td>{{ invitation.email }}</td>
+            <td{% if invitation.status == 'draft' %} class="text-warning"{% endif %}>{{ invitation.get_status_display }}</td>
+            <td>{{ invitation.get_invitation_type_display }}</td>
+            <td>{{ invitation.created_by.first_name }} {{ invitation.created_by.last_name }}</td>
+            <td>{{ invitation.created }}</td>
+            <td>
+                {% if invitation.times_sent %}
+                    <strong>{{ invitation.times_sent }}</strong> time{{ invitation.times_sent|pluralize }}
+                    &middot; {{ invitation.date_sent_last|timesince }} ago
+                {% else %}
+                    -
+                {% endif %}
+            </td>
+            {% if perms.scipost.can_manage_registration_invitations %}
+                <td>
+                    <ul class="pl-3 mb-0">
+                        {% if invitation.status == 'draft' %}
+                            <li><a href="{% url 'invitations:update' invitation.id %}">Edit or send</a></li>
+                            <li><a href="{% url 'invitations:mark' invitation.id 'sent' %}">Mark as sent</a></li>
+                        {% elif invitation.status == 'sent' or invitation.status == 'edited' %}
+                            <li><a href="{% url 'invitations:send_reminder' invitation.id %}">Send reminder</a></li>
+                        {% endif %}
+                    </ul>
+                </td>
+                <td>
+                    <ul class="mb-0">
+                        {% for ac in invitation|associated_contributors %}
+                            <li>
+                                <a href="{% url 'invitations:map_to_contributor' pk=invitation.id contributor_id=ac.id %}">Map to {{ ac.user.first_name }} {{ ac.user.last_name }}</a>
+                            </li>
+                        {% endfor %}
+                        <li><a href="{% url 'invitations:add_citation' invitation.id %}">Add new Citation to Invitation</a></li>
+                    </ul>
+                </td>
+            {% else %}
+                <td colspan="2">
+                    <ul class="pl-3 mb-0">
+                        <li><a href="{% url 'invitations:add_citation' invitation.id %}">Add new Citation to Invitation</a></li>
+                        {% if invitation.status == 'draft' and invitation.created_by == request.user %}
+                            <li><a href="{% url 'invitations:update' invitation.id %}">Edit Invitation</a></li>
+                        {% endif %}
+                    </ul>
+                </td>
+            {% endif %}
+          </tr>
+        {% empty %}
+            <tr>
+                <td colspan="9">No Invitations found.</td>
+            </tr>
+        {% endfor %}
+    </tbody>
+</table>
diff --git a/invitations/tests.py b/invitations/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/invitations/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/invitations/urls.py b/invitations/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..d53b7ea3ef19fb78c221549ca4d81d78fedc8f33
--- /dev/null
+++ b/invitations/urls.py
@@ -0,0 +1,26 @@
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+    url(r'^$', views.RegistrationInvitationsView.as_view(), name='list'),
+    url(r'^new$', views.create_registration_invitation_or_citation, name='new'),
+    url(r'^(?P<pk>[0-9]+)$', views.RegistrationInvitationsUpdateView.as_view(), name='update'),
+    url(r'^(?P<pk>[0-9]+)/add_citation$', views.RegistrationInvitationsAddCitationView.as_view(),
+        name='add_citation'),
+    url(r'^(?P<pk>[0-9]+)/delete$', views.RegistrationInvitationsDeleteView.as_view(),
+        name='delete'),
+    url(r'^(?P<pk>[0-9]+)/mark/(?P<label>sent)$', views.RegistrationInvitationsMarkView.as_view(),
+        name='mark'),
+    url(r'^(?P<pk>[0-9]+)/map_to_contributor/(?P<contributor_id>[0-9]+)$',
+        views.RegistrationInvitationsMapToContributorView.as_view(),
+        name='map_to_contributor'),
+    url(r'^(?P<pk>[0-9]+)/send_reminder$', views.RegistrationInvitationsReminderView.as_view(),
+        name='send_reminder'),
+    url(r'^cleanup$', views.cleanup, name='cleanup'),
+
+    url(r'^citations$', views.CitationNotificationsView.as_view(),
+        name='citation_notification_list'),
+    url(r'^citations/(?P<pk>[0-9]+)$', views.CitationNotificationsProcessView.as_view(),
+        name='citation_notification_process'),
+]
diff --git a/invitations/utils.py b/invitations/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ba915726de3348db196f64b66607aff45c18fc6
--- /dev/null
+++ b/invitations/utils.py
@@ -0,0 +1,31 @@
+from common.utils import BaseMailUtil
+
+
+class Utils(BaseMailUtil):
+    mail_sender = 'invitations@scipost.org'
+    mail_sender_title = 'SciPost Invitation'
+
+    @classmethod
+    def invite_contributor_email(cls):
+        """
+        Send email to unregistered people inviting them to become a SciPost Contributor.
+        Requires context to contain 'registration_invitation' (RegistrationInvitation instance).
+        """
+        raise NotImplementedError('invite_contributor_email')
+
+    @classmethod
+    def invite_contributor_reminder_email(cls):
+        """
+        Send reminder(!) email to unregistered people inviting them to become a SciPost
+        Contributor.
+        Requires context to contain 'registration_invitation'(RegistrationInvitation instance).
+        """
+        raise NotImplementedError('invite_contributor_reminder_email')
+
+    @classmethod
+    def citation_notifications_email(cls):
+        """
+        Send email to a SciPost Contributor about a Citation Notification that's been created
+        for him/her. Requires context to contain 'notifications' (list of CitationNotifications).
+        """
+        raise NotImplementedError('citation_notifications_email')
diff --git a/invitations/views.py b/invitations/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..60e8351a36c128701dbb63b0f83886d9373f8692
--- /dev/null
+++ b/invitations/views.py
@@ -0,0 +1,201 @@
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required, permission_required
+from django.db import transaction
+from django.shortcuts import render, redirect
+from django.urls import reverse_lazy, reverse
+from django.views.generic.list import ListView
+from django.views.generic.edit import UpdateView, DeleteView
+
+from .forms import RegistrationInvitationForm, RegistrationInvitationReminderForm,\
+    RegistrationInvitationMarkForm, RegistrationInvitationMapToContributorForm,\
+    CitationNotificationForm, SuggestionSearchForm, RegistrationInvitationFilterForm,\
+    CitationNotificationProcessForm, RegistrationInvitationAddCitationForm
+from .mixins import RequestArgumentMixin, PermissionsMixin, SaveAndSendFormMixin
+from .models import RegistrationInvitation, CitationNotification
+
+from scipost.models import Contributor
+from mails.mixins import MailEditorMixin
+
+
+class RegistrationInvitationsView(PermissionsMixin, ListView):
+    permission_required = 'scipost.can_create_registration_invitations'
+    queryset = RegistrationInvitation.objects.no_response()
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['count_in_draft'] = RegistrationInvitation.objects.drafts().count()
+        context['count_pending'] = RegistrationInvitation.objects.sent().count()
+        search_form = RegistrationInvitationFilterForm(self.request.GET or None)
+        if search_form.is_valid():
+            context['object_list'] = search_form.search(context['object_list'])
+        context['object_list'] = context['object_list'].order_by('date_sent_last', 'last_name')
+        context['search_form'] = search_form
+        return context
+
+    def get_queryset(self, *args, **kwargs):
+        qs = super().get_queryset(*args, **kwargs)
+        if not self.request.user.has_perm('scipost.can_invite_fellows'):
+            qs = qs.not_for_fellows()
+        return qs
+
+
+class CitationNotificationsView(PermissionsMixin, ListView):
+    permission_required = 'scipost.can_manage_registration_invitations'
+    queryset = CitationNotification.objects.unprocessed().prefetch_related(
+        'invitation', 'contributor', 'contributor__user')
+
+
+class CitationNotificationsProcessView(PermissionsMixin, RequestArgumentMixin,
+                                       MailEditorMixin, UpdateView):
+    permission_required = 'scipost.can_manage_registration_invitations'
+    form_class = CitationNotificationProcessForm
+    queryset = CitationNotification.objects.unprocessed()
+    success_url = reverse_lazy('invitations:citation_notification_list')
+    mail_code = 'citation_notification'
+
+    @transaction.atomic
+    def form_valid(self, form):
+        """
+        Form is valid; use the MailEditorMixin to send out the mail if
+        (possible) Contributor didn't opt-out from mails.
+        """
+        form.get_all_notifications().update(processed=True)
+        contributor = form.get_all_notifications().filter(contributor__isnull=False).first()
+        self.send_mail = (contributor and contributor.accepts_SciPost_emails) or not contributor
+        return super().form_valid(form)
+
+
+@login_required
+@permission_required('scipost.can_create_registration_invitations', raise_exception=True)
+@transaction.atomic
+def create_registration_invitation_or_citation(request):
+    """
+    Create a new Registration Invitation or Citation Notification, depending whether
+    it is meant for an already existing Contributor or not.
+    """
+    contributors = []
+    suggested_invitations = []
+    declined_invitations = []
+
+    # Only take specific GET data to prevent for unexpected bound forms.
+    search_data = {}
+    initial_search_data = {}
+    if request.GET.get('last_name'):
+        search_data['last_name'] = request.GET['last_name']
+    if request.GET.get('prefill-last_name'):
+        initial_search_data['last_name'] = request.GET['prefill-last_name']
+    suggestion_search_form = SuggestionSearchForm(search_data or None,
+                                                  initial=initial_search_data)
+    if suggestion_search_form.is_valid():
+        contributors, suggested_invitations, declined_invitations = suggestion_search_form.search()
+    citation_form = CitationNotificationForm(request.POST or None, contributors=contributors,
+                                             prefix='notification', request=request)
+
+    # New citation is related to a Contributor: RegistationInvitation
+    invitation_form = RegistrationInvitationForm(request.POST or None, request=request,
+                                                 prefix='invitation',
+                                                 initial=initial_search_data)
+    if invitation_form.is_valid():
+        invitation_form.save()
+        messages.success(request, 'New Registration Invitation created')
+        if request.POST.get('save') == 'save_and_create':
+            return redirect('invitations:new')
+        return redirect('invitations:list')
+
+    # New citation is related to a Contributor: CitationNotification
+    if citation_form.is_valid():
+        citation_form.save()
+        messages.success(request, 'New Citation Notification created')
+        if request.POST.get('save') == 'save_and_create':
+            return redirect('invitations:new')
+        return redirect('invitations:list')
+
+    context = {
+        'suggestion_search_form': suggestion_search_form,
+        'citation_form': citation_form,
+        'suggested_invitations': suggested_invitations,
+        'declined_invitations': declined_invitations,
+        'invitation_form': invitation_form,
+    }
+    return render(request, 'invitations/registrationinvitation_form_add_new.html', context)
+
+
+class RegistrationInvitationsUpdateView(RequestArgumentMixin, PermissionsMixin,
+                                        SaveAndSendFormMixin, MailEditorMixin, UpdateView):
+    permission_required = 'scipost.can_create_registration_invitations'
+    form_class = RegistrationInvitationForm
+    mail_code = 'registration_invitation'
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['invitation_form'] = context['form']
+        return context
+
+    def get_success_url(self):
+        if self.request.POST.get('save') == 'save_and_create':
+            return reverse('invitations:new')
+        return reverse('invitations:list')
+
+    def get_queryset(self, *args, **kwargs):
+        qs = RegistrationInvitation.objects.drafts()
+        if not self.request.user.has_perm('scipost.can_invite_fellows'):
+            qs = qs.not_for_fellows()
+        if not self.request.user.has_perm('scipost.can_manage_registration_invitations'):
+            qs = qs.invited_by(self.request.user)
+        return qs
+
+
+class RegistrationInvitationsAddCitationView(RequestArgumentMixin, PermissionsMixin, UpdateView):
+    permission_required = 'scipost.can_create_registration_invitations'
+    form_class = RegistrationInvitationAddCitationForm
+    template_name = 'invitations/registrationinvitation_form_add_citation.html'
+    success_url = reverse_lazy('invitations:list')
+    queryset = RegistrationInvitation.objects.no_response()
+
+
+class RegistrationInvitationsMarkView(RequestArgumentMixin, PermissionsMixin, UpdateView):
+    permission_required = 'scipost.can_manage_registration_invitations'
+    queryset = RegistrationInvitation.objects.drafts()
+    form_class = RegistrationInvitationMarkForm
+    template_name = 'invitations/registrationinvitation_form_mark_as.html'
+    success_url = reverse_lazy('invitations:list')
+
+
+class RegistrationInvitationsMapToContributorView(RequestArgumentMixin, PermissionsMixin,
+                                                  UpdateView):
+    permission_required = 'scipost.can_manage_registration_invitations'
+    model = RegistrationInvitation
+    form_class = RegistrationInvitationMapToContributorForm
+    template_name = 'invitations/registrationinvitation_form_map_to_contributor.html'
+    success_url = reverse_lazy('invitations:list')
+
+
+class RegistrationInvitationsReminderView(RequestArgumentMixin, PermissionsMixin,
+                                          SaveAndSendFormMixin, MailEditorMixin, UpdateView):
+    permission_required = 'scipost.can_manage_registration_invitations'
+    queryset = RegistrationInvitation.objects.sent()
+    success_url = reverse_lazy('invitations:list')
+    form_class = RegistrationInvitationReminderForm
+    template_name = 'invitations/registrationinvitation_reminder_form.html'
+    mail_code = 'registration_invitation_reminder'
+
+
+class RegistrationInvitationsDeleteView(PermissionsMixin, DeleteView):
+    permission_required = 'scipost.can_manage_registration_invitations'
+    model = RegistrationInvitation
+    success_url = reverse_lazy('invitations:list')
+
+
+@login_required
+@permission_required('scipost.can_manage_registration_invitations', raise_exception=True)
+def cleanup(request):
+    """
+    Compares the email addresses of invitations with those in the
+    database of registered Contributors. Flags overlaps.
+    """
+    contributor_email_list = Contributor.objects.values_list('user__email', flat=True)
+    invitations = RegistrationInvitation.objects.sent().filter(email__in=contributor_email_list)
+    context = {
+        'invitations': invitations
+    }
+    return render(request, 'invitations/registrationinvitation_cleanup.html', context)
diff --git a/journals/templatetags/lookup.py b/journals/templatetags/lookup.py
index bdfeb24b0ce148daab9e22cc133a37abf5e7cc2a..9323d78aa1ca21a73e50a8022eb758cf7c90e8c3 100644
--- a/journals/templatetags/lookup.py
+++ b/journals/templatetags/lookup.py
@@ -28,4 +28,4 @@ class PublicationLookup(LookupChannel):
         Right now only used for draft registration invitations. May be extended in the
         future for other purposes as well.
         """
-        return request.user.has_perm('can_draft_registration_invitations')
+        return request.user.has_perm('can_create_registration_invitations')
diff --git a/mailing_lists/views.py b/mailing_lists/views.py
index 07792fff8517e31ac61e744087daeb4fec78637b..0512a16009c9678e76dc52c42e1673cac71e307e 100644
--- a/mailing_lists/views.py
+++ b/mailing_lists/views.py
@@ -9,7 +9,7 @@ from django.views.generic.list import ListView
 from django.core.urlresolvers import reverse
 from django.shortcuts import redirect, get_object_or_404
 
-from scipost.models import RegistrationInvitation
+from invitations.models import RegistrationInvitation
 
 from .forms import MailchimpUpdateForm
 from .models import MailchimpList
diff --git a/mails/forms.py b/mails/forms.py
index a7a8b241a3b22343728d21d1e28536af52da02b4..b69d0d037d23de0d2663445ba2e806606e7c0a1c 100644
--- a/mails/forms.py
+++ b/mails/forms.py
@@ -1,133 +1,62 @@
-import re
-import json
-import inspect
-from html2text import HTML2Text
-
 from django import forms
-from django.core.mail import EmailMultiAlternatives
-from django.contrib.auth import get_user_model
-from django.conf import settings
-from django.template import loader
-
-from scipost.models import Contributor
 
+from .mixins import MailUtilsMixin
 from .widgets import SummernoteEditor
 
 
-class EmailTemplateForm(forms.Form):
+class EmailTemplateForm(forms.Form, MailUtilsMixin):
     subject = forms.CharField(max_length=250, label="Subject*")
     text = forms.CharField(widget=SummernoteEditor, label="Text*")
     extra_recipient = forms.EmailField(label="Optional: bcc this email to", required=False)
+    prefix = 'mail_form'
 
     def __init__(self, *args, **kwargs):
-        self.mail_code = kwargs.pop('mail_code')
-        self.mail_fields = None
-        super().__init__(*args)
-
-        # Gather data
-        mail_template = loader.get_template('mail_templates/%s.html' % self.mail_code)
-        mail_template = mail_template.render(kwargs)
-        # self.doc = html.fromstring(mail_template)
-        # self.doc2 = self.doc.text_content()
-        # print(self.doc2)
-
-        json_location = '%s/mails/templates/mail_templates/%s.json' % (settings.BASE_DIR,
-                                                                       self.mail_code)
-        self.mail_data = json.loads(open(json_location).read())
-
-        # Object
-        self.object = kwargs.get(self.mail_data.get('context_object', ''), None)
-        self.recipient = None
-        if self.object:
-            recipient = self.object
-            for attr in self.mail_data.get('to_address').split('.'):
-                recipient = getattr(recipient, attr)
-                if inspect.ismethod(recipient):
-                    recipient = recipient()
-            self.recipient = recipient
+        # This form shouldn't be is_bound==True is there is any non-relavant POST data given.
+        data = args[0]
+        if '%s-subject' % self.prefix in data.keys():
+            data = {
+                '%s-subject' % self.prefix: data.get('%s-subject' % self.prefix),
+                '%s-text' % self.prefix: data.get('%s-text' % self.prefix),
+                '%s-extra_recipient' % self.prefix: data.get('%s-extra_recipient' % self.prefix),
+            }
+        else:
+            data = None
+        super().__init__(data, *args, **kwargs)
 
-        if not self.recipient:
+        if not self.original_recipient:
             self.fields['extra_recipient'].label = "Send this email to"
             self.fields['extra_recipient'].required = True
 
         # Set the data as initials
-        self.fields['text'].initial = mail_template
+        self.fields['text'].initial = self.mail_template
         self.fields['subject'].initial = self.mail_data['subject']
 
     def save_data(self):
         # Get text and html
-        html_message = self.cleaned_data['text']
-        handler = HTML2Text()
-        message = handler.handle(html_message)
+        self.html_message = self.cleaned_data['text']
+        self.subject = self.cleaned_data['subject']
+        self.validate_message()
 
         # Get recipients list. Try to send through BCC to prevent privacy issues!
-        bcc_list = []
-        if self.mail_data.get('bcc_to', False) and self.object:
-            if re.match("[^@]+@[^@]+\.[^@]+", self.mail_data.get('bcc_to')):
-                bcc_list = [self.mail_data.get('bcc_to')]
-            else:
-                bcc_to = self.object
-                for attr in self.mail_data.get('bcc_to').split('.'):
-                    bcc_to = getattr(bcc_to, attr)
-
-                if not isinstance(bcc_to, list):
-                    bcc_list = [bcc_to]
-                else:
-                    bcc_list = bcc_to
-        elif re.match("[^@]+@[^@]+\.[^@]+", self.mail_data.get('bcc_to', '')):
-            bcc_list = [self.mail_data.get('bcc_to')]
-
-        if self.cleaned_data.get('extra_recipient') and self.recipient:
-            bcc_list.append(self.cleaned_data.get('extra_recipient'))
-        elif self.cleaned_data.get('extra_recipient') and not self.recipient:
-            self.recipient = [self.cleaned_data.get('extra_recipient')]
-        elif not self.recipient:
+        if self.cleaned_data.get('extra_recipient') and self.original_recipient:
+            self.bcc_list.append(self.cleaned_data.get('extra_recipient'))
+        elif self.cleaned_data.get('extra_recipient') and not self.original_recipient:
+            self.original_recipient = [self.cleaned_data.get('extra_recipient')]
+        elif not self.original_recipient:
             self.add_error('extra_recipient', 'Please fill the bcc field to send the mail.')
 
-        # Check the send list
-        if isinstance(self.recipient, list):
-            recipients = self.recipient
-        elif not isinstance(self.recipient, str):
-            try:
-                recipients = list(self.recipient)
-            except TypeError:
-                recipients = [self.recipient]
-        else:
-            recipients = [self.recipient]
-        recipients = list(recipients)
-
-        # Check if email needs to be taken from instance
-        _recipients = []
-        for recipient in recipients:
-            if isinstance(recipient, Contributor):
-                _recipients.append(recipient.user.email)
-            elif isinstance(recipient, get_user_model()):
-                _recipients.append(recipient.email)
-            elif isinstance(recipient, str):
-                _recipients.append(recipient)
-
-        self.mail_fields = {
-            'subject': self.cleaned_data['subject'],
-            'message': message,
-            'html_message': html_message,
-            'recipients': _recipients,
-            'bcc_list': bcc_list,
-        }
+        self.validate_recipients()
+        self.save_mail_data()
 
     def clean(self):
         data = super().clean()
         self.save_data()
         return data
 
-    def send(self):
-        # Send the mail
-        email = EmailMultiAlternatives(
-            self.mail_fields['subject'],
-            self.mail_fields['message'],
-            '%s <%s>' % (self.mail_data.get('from_address_name', 'SciPost'),
-                         self.mail_data.get('from_address', 'no-reply@scipost.org')),  # From
-            self.mail_fields['recipients'],  # To
-            bcc=self.mail_fields['bcc_list'],
-            reply_to=[self.mail_data.get('from_address', 'no-reply@scipost.org')])
-        email.attach_alternative(self.mail_fields['html_message'], 'text/html')
-        email.send(fail_silently=False)
+
+class HiddenDataForm(forms.Form):
+    def __init__(self, form, *args, **kwargs):
+        super().__init__(form.data, *args, **kwargs)
+        for name, field in form.fields.items():
+            self.fields[name] = field
+            self.fields[name].widget = forms.HiddenInput()
diff --git a/mails/mixins.py b/mails/mixins.py
new file mode 100644
index 0000000000000000000000000000000000000000..029097f2431274a82fb2de98a3837c6ba5594cb9
--- /dev/null
+++ b/mails/mixins.py
@@ -0,0 +1,214 @@
+import re
+import json
+import inspect
+from html2text import HTML2Text
+
+from django.core.mail import EmailMultiAlternatives
+from django.contrib import messages
+from django.contrib.auth import get_user_model
+from django.conf import settings
+from django.template import loader
+
+from scipost.models import Contributor
+
+
+from . import forms
+
+
+class MailEditorMixin:
+    """
+    Use MailEditorMixin in edit CBVs to automatically implement the mail editor as
+    a post-form_valid hook.
+
+    The view must specify the `mail_code` variable.
+    """
+    object = None
+    mail_form = None
+    has_permission_to_send_mail = True
+
+    def __init__(self, *args, **kwargs):
+        if not self.mail_code:
+            raise AttributeError(self.__class__.__name__ + ' object has no attribute `mail_code`')
+        super().__init__(*args, **kwargs)
+
+    def get_template_names(self):
+        """
+        The mail editor form has its own template.
+        """
+        if self.mail_form and not self.mail_form.is_valid():
+            return ['mails/mail_form.html']
+        return super().get_template_names()
+
+    def post(self, request, *args, **kwargs):
+        """
+        Handle POST requests, but interpect the data if the mail form data isn't valid.
+        """
+        if not self.has_permission_to_send_mail:
+            # Don't use the mail form; don't send out the mail.
+            return super().post(request, *args, **kwargs)
+        self.object = self.get_object()
+        form = self.get_form()
+        if form.is_valid():
+            self.mail_form = forms.EmailTemplateForm(request.POST or None,
+                                                     mail_code=self.mail_code,
+                                                     instance=self.object)
+            if self.mail_form.is_valid():
+                return self.form_valid(form)
+
+            return self.render_to_response(
+                self.get_context_data(form=self.mail_form,
+                                      transfer_data_form=forms.HiddenDataForm(form)))
+        else:
+            return self.form_invalid(form)
+
+    def form_valid(self, form):
+        """
+        If both the regular form and mailing form are valid, save the form and run the mail form.
+        """
+        # Don't use the mail form; don't send out the mail.
+        if not self.has_permission_to_send_mail:
+            return super().form_valid(form)
+
+        try:
+            self.mail_form.send()
+        except AttributeError:
+            # self.mail_form is None
+            raise AttributeError('Did you check the order in which MailEditorMixin is used?')
+        messages.success(self.request, 'Mail sent')
+        return super().form_valid(form)
+
+
+class MailUtilsMixin:
+    """
+    This mixin takes care of inserting the default data into the Utils or Form.
+    """
+    object = None
+    mail_fields = {}
+    mail_template = ''
+    html_message = ''
+    message = ''
+
+    def __init__(self, *args, **kwargs):
+        self.mail_code = kwargs.pop('mail_code')
+        self.instance = kwargs.pop('instance', None)
+
+        # Gather meta data
+        json_location = '%s/mails/templates/mail_templates/%s.json' % (settings.BASE_DIR,
+                                                                       self.mail_code)
+        try:
+            self.mail_data = json.loads(open(json_location).read())
+        except OSError:
+            raise NotImplementedError(('You did not create a valid .html and .json file '
+                                       'for mail_code: %s' % self.mail_code))
+
+        # Save central object/instance
+        self.object = self.get_object(**kwargs)
+
+        # Digest the templates
+        mail_template = loader.get_template('mail_templates/%s.html' % self.mail_code)
+        if self.instance and self.mail_data.get('context_object'):
+            kwargs[self.mail_data['context_object']] = self.instance
+        self.mail_template = mail_template.render(kwargs)
+
+        # Gather Recipients data
+        self.original_recipient = ''
+        if self.object:
+            recipient = self.object
+            for attr in self.mail_data.get('to_address').split('.'):
+                recipient = getattr(recipient, attr)
+                if inspect.ismethod(recipient):
+                    recipient = recipient()
+            self.original_recipient = recipient
+
+        self.subject = self.mail_data['subject']
+
+
+    def validate_recipients(self):
+        # Get recipients list. Try to send through BCC to prevent privacy issues!
+        self.bcc_list = []
+        if self.mail_data.get('bcc_to', False) and self.object:
+            if re.match("[^@]+@[^@]+\.[^@]+", self.mail_data.get('bcc_to')):
+                self.bcc_list = [self.mail_data.get('bcc_to')]
+            else:
+                bcc_to = self.object
+                for attr in self.mail_data.get('bcc_to').split('.'):
+                    bcc_to = getattr(bcc_to, attr)
+
+                if not isinstance(bcc_to, list):
+                    self.bcc_list = [bcc_to]
+                else:
+                    self.bcc_list = bcc_to
+        elif re.match("[^@]+@[^@]+\.[^@]+", self.mail_data.get('bcc_to', '')):
+            self.bcc_list = [self.mail_data.get('bcc_to')]
+
+        # Check the send list
+        if isinstance(self.original_recipient, list):
+            recipients = self.original_recipient
+        elif not isinstance(self.original_recipient, str):
+            try:
+                recipients = list(self.original_recipient)
+            except TypeError:
+                recipients = [self.original_recipient]
+        else:
+            recipients = [self.original_recipient]
+        recipients = list(recipients)
+
+        # Check if email needs to be taken from an instance
+        _recipients = []
+        for recipient in recipients:
+            if isinstance(recipient, Contributor):
+                _recipients.append(recipient.user.email)
+            elif isinstance(recipient, get_user_model()):
+                _recipients.append(recipient.email)
+            elif isinstance(recipient, str):
+                _recipients.append(recipient)
+        self.recipients = _recipients
+
+    def validate_message(self):
+        if not self.html_message:
+            self.html_message = self.mail_template
+        handler = HTML2Text()
+        self.message = handler.handle(self.html_message)
+
+    def validate(self):
+        """
+        Ease workflow by called this wrapper validation method.
+
+        Only to be used when the default data is used, eg. not in the EmailTemplateForm.
+        """
+        self.validate_message()
+        self.validate_recipients()
+        self.save_mail_data()
+
+    def save_mail_data(self):
+        self.mail_fields = {
+            'subject': self.subject,
+            'message': self.message,
+            'html_message': self.html_message,
+            'recipients': self.recipients,
+            'bcc_list': self.bcc_list,
+        }
+
+    def get_object(self, **kwargs):
+        if self.object:
+            return self.object
+        if self.instance:
+            return self.instance
+
+        if self.mail_data.get('context_object'):
+            return kwargs.get(self.mail_data['context_object'], None)
+
+    def send(self):
+        # Send the mail
+        email = EmailMultiAlternatives(
+            self.mail_fields['subject'],
+            self.mail_fields['message'],
+            '%s <%s>' % (self.mail_data.get('from_address_name', 'SciPost'),
+                         self.mail_data.get('from_address', 'no-reply@scipost.org')),  # From
+            self.mail_fields['recipients'],  # To
+            bcc=self.mail_fields['bcc_list'],
+            reply_to=[self.mail_data.get('from_address', 'no-reply@scipost.org')])
+        email.attach_alternative(self.mail_fields['html_message'], 'text/html')
+        email.send(fail_silently=False)
+        if self.object and hasattr(self.object, 'mail_sent'):
+            self.object.mail_sent()
diff --git a/mails/templates/mail_templates/citation_notification.html b/mails/templates/mail_templates/citation_notification.html
new file mode 100644
index 0000000000000000000000000000000000000000..61021214c03b162b488e198f6d853bdc45256d03
--- /dev/null
+++ b/mails/templates/mail_templates/citation_notification.html
@@ -0,0 +1,65 @@
+
+Dear {{ notification.get_title }} {{ notification.last_name }},
+
+<br>
+<br>
+
+<p>
+    We would like to notify you that your work has been cited in
+
+    {% if notification.related_notifications.for_publications %}
+        {% if notification.related_notifications.for_publications|length > 1 %}{{ notification.related_notifications.for_publications|length }} papers{% else %}a paper{% endif %}
+        published by SciPost:
+
+        <ul>
+            {% for notification in notification.related_notifications.for_publications %}
+                <li>
+                    <a href="https://doi.org/{{ notification.publication.doi_label }}">{{ notification.publication.citation }}</a>
+                    <br>
+                    {{ notification.publication.title }}
+                    <br>
+                    <i>by {{ notification.publication.author_list }}</i>
+                </li>
+            {% endfor %}
+        </ul>
+    {% endif %}
+
+    {% if notification.related_notifications.for_submissions %}
+        {% if notification.related_notifications.for_submissions|length > 1 %}{{ notification.related_notifications.for_submissions|length }} manuscripts{% else %}a manuscript{% endif %}
+        submitted to SciPost,
+
+        <ul>
+            {% for notification in notification.related_notifications.for_submissions %}
+                <li>
+                    {{ notification.submission.title }}
+                    <br>
+                    <i>by {{ notification.submission.author_list }}</i>
+                    <br>
+                    <a href="https://scipost.org/{{ notification.submission.get_absolute_url }}">View the submission's page</a>
+                </li>
+            {% endfor %}
+        </ul>
+    {% endif %}
+</p>
+
+{% if notification.related_notifications.for_publications %}
+    <p>We hope you will find this paper of interest to your own research.</p>
+{% else %}
+    <p>You might for example consider reporting or commenting on the above submission before the refereeing deadline.</p>
+{% endif %}
+
+<p>
+    Best regards,
+    <br>
+    The SciPost Team
+</p>
+
+{% if notification.get_first_related_contributor %}
+    <p style="font-size: 10px;">
+        Don\'t want to receive such emails? <a href="https://scipost.org/{% url 'scipost:unsubscribe' notification.get_first_related_contributor.id notification.get_first_related_contributor.activation_key %}">Unsubscribe</a>
+    </p>
+{% endif %}
+
+
+
+{% include 'email/_footer.html' %}
diff --git a/mails/templates/mail_templates/citation_notification.json b/mails/templates/mail_templates/citation_notification.json
new file mode 100644
index 0000000000000000000000000000000000000000..a9314819355439cff7eb54d7fae2942f9b647524
--- /dev/null
+++ b/mails/templates/mail_templates/citation_notification.json
@@ -0,0 +1,8 @@
+{
+    "subject": "SciPost: citation notification",
+    "to_address": "email",
+    "bcc_to": "admin@scipost.org",
+    "from_address_name": "SciPost Admin",
+    "from_address": "admin@scipost.org",
+    "context_object": "notification"
+}
diff --git a/mails/templates/mail_templates/registration_invitation.html b/mails/templates/mail_templates/registration_invitation.html
new file mode 100644
index 0000000000000000000000000000000000000000..fceaa5f9956115a983731e522ee8a74e28fb4f16
--- /dev/null
+++ b/mails/templates/mail_templates/registration_invitation.html
@@ -0,0 +1,204 @@
+{% if invitation.invitation_type == 'F' %}
+    <strong>RE: Invitation to join the Editorial College of SciPost</strong>
+    <br>
+{% endif %}
+
+Dear {% if invitation.message_style == 'F' %}{{ invitation.get_title_display }} {{ invitation.last_name }}{% else %}{{ invitation.first_name }}{% endif %},
+
+<br><br>
+
+{% if invitation.personal_message %}
+    {{ invitation.personal_message|linebreaksbr }}
+    <br>
+{% endif %}
+
+
+{% if invitation.invitation_type == 'R' %}
+    {# Referee invite #}
+    <p>
+        We would hereby like to cordially invite you to become a Contributor on SciPost (this is required in order to deliver reports; our records show that you are not yet registered);
+        for your convenience, we have prepared a pre-filled <a href="https://scipost.org/invitation/{{ invitation.invitation_key }}">registration form</a> for you.
+        After activation of your registration, you will be allowed to contribute, in particular by providing referee reports.
+    </p>
+    <p>
+        To ensure timely processing of the submission (out of respect for the authors),
+        we would appreciate a quick accept/decline response from you, ideally within the next 2 days.
+    </p>
+    <p>
+        If you are <strong>not</strong> able to provide a Report, you can let us know by simply <a href="https://scipost.org/submissions/decline_ref_invitation/{{ invitation.invitation_key }}"> clicking here</a>.
+    </p>
+    <p>
+        If you are able to provide a Report, you can confirm this after registering and logging in (you will automatically be prompted for a confirmation).
+    </p>
+    <p>
+        We very much hope that we can count on your expertise,
+        <br>
+        Many thanks in advance,
+        <br>
+        The SciPost Team
+    </p>
+
+{% elif invitation.invitation_type == 'C' %}
+    {# "Regular" invite #}
+    {% if invitation.citation_notifications.for_publications %}
+        <p>
+            Your work has been cited in
+            {% if invitation.citation_notifications.for_publications|length > 1 %}{{ invitation.citation_notifications.for_publications|length }} papers{% else %}a paper{% endif %}
+            published by SciPost:
+        </p>
+
+        <ul>
+            {% for notification in invitation.citation_notifications.for_publications %}
+                <li>
+                    <a href="https://doi.org/{{ notification.publication.doi_label }}">{{ notification.publication.citation }}</a>
+                    <br>
+                    {{ notification.publication.title }}
+                    <br>
+                    <i>by {{ notification.publication.author_list }}</i>
+                </li>
+            {% endfor %}
+        </ul>
+    {% endif %}
+
+    {% if invitation.citation_notifications.for_submissions %}
+        <p>
+            Your work has been cited in
+            {% if invitation.citation_notifications.for_submissions|length > 1 %}{{ invitation.citation_notifications.for_submissions|length }} manuscripts{% else %}a manuscript{% endif %}
+            submitted to SciPost,
+        </p>
+
+        <ul>
+            {% for notification in invitation.citation_notifications.for_submissions %}
+                <li>
+                    {{ notification.submission.title }}
+                    <br>
+                    <i>by {{ notification.submission.author_list }}</i>
+                    <br>
+                    <a href="https://scipost.org/{{ notification.submission.get_absolute_url }}">View the submission's page</a>
+                </li>
+            {% endfor %}
+        </ul>
+    {% endif %}
+    <p>
+        I would hereby like to use this opportunity to quickly introduce you to the SciPost initiative, and to invite you to become an active Contributor.
+    </p>
+    <p>
+        In summary, SciPost.org is a publication portal managed by
+        professional scientists, offering (among others) high-quality
+        Open Access journals with innovative forms of refereeing, and a
+        means of commenting on all existing literature. SciPost is established as
+        a not-for-profit foundation devoted to serving the interests of the
+        international scientific community.
+    </p>
+    <p>
+        The site is anchored at <a href="https://scipost.org">scipost.org</a>.
+        Many further details about SciPost, its principles, ideals and implementation can be found at
+        the <a href="https://scipost.org/about">about</a>
+        and <a href="https://scipost.org/FAQ">FAQ</a> pages.</p>
+        <p>As a professional academic, you can register at the
+        <a href="https://scipost.org/register">registration page</a>,
+        enabling you to contribute to the site's contents, for example by offering submissions, reports and comments.
+    </p>
+    <p>
+        For your convenience, a partly pre-filled <a href="https://scipost.org/invitation/{{ invitation.invitation_key }}">registration form</a>
+        has been prepared for you (you can in any case still register at the
+        <a href="https://scipost.org/register">registration page</a>).
+    </p>
+    <p>
+        If you do develop sympathy for the initiative, besides participating in the
+        online platform, we would be very grateful if you considered submitting a
+        publication to one of the journals within the near future, in order to help
+        establish their reputation. We'll also be looking forward to your reaction,
+        comments and suggestions about the initiative, which we hope you will find
+        useful to your work as a professional scientist.
+    </p>
+    <p>
+        Many thanks in advance for taking a few minutes to look into it,
+        <br>
+        On behalf of the SciPost Foundation,
+        <br>
+        <br>
+        {{ invitation.invited_by.contributor.get_title_display }} {{ invitation.invited_by.first_name }} {{ invitation.invited_by.last_name }}
+    </p>
+{% elif invitation.invitation_type == 'F' %}
+    {# Fellow invite #}
+    <p>
+        You will perhaps have already heard about SciPost, a publication
+        portal established by and for professional scientists.
+    </p>
+    <p>
+        SciPost.org is legally based on a not-for-profit foundation and will
+        operate in perpetuity as a non-commercial entity at the exclusive service
+        of the academic sector, bringing a cost-slashing alternative to existing practices.
+    </p>
+    <p>
+        SciPost offers a collection of two-way open
+        access (no subscription fees, no author fees) journals with extremely
+        stringent (peer-witnessed) refereeing, overseen by
+        our Editorial College (exclusively composed
+        of established, professionally practising scientists). The whole process is
+        designed to ensure the highest achievable scientific quality while making the
+        editorial workflow as light and efficient as possible.
+    </p>
+    <p>
+        To go straight to the point, on behalf of the SciPost Foundation
+        and in view of your professional expertise, I hereby would
+        like to invite you to become an Editorial Fellow and thus join the
+        Editorial College of SciPost Physics.
+    </p>
+    <p>
+        Please note that only well-known and respected senior academics are
+        being contacted for this purpose. Academic reputation and involvement
+        in the community are the most important criteria guiding our
+        considerations of who should belong to the Editorial College.
+    </p>
+    <p>
+        To help you in considering this, it would be best if you were to take
+        the time to look at the website itself, which is anchored at scipost.org.
+        Besides looking around the site, you can also personally register
+        (to become a Contributor, without necessarily committing to membership
+        of the Editorial College, this to be discussed separately) by visiting
+        the following <a href="https://scipost.org/invitation/{{ invitation.cited_in_publication.citation }}">
+        single-use link</a>, containing a partly pre-filled form for your convenience.
+    </p>
+    <p>
+        Many details about the initiative
+        can then be found at scipost.org/about and at scipost.org/FAQ.
+        Functioning of the College will proceed according to the by-laws set
+        out in scipost.org/EdCol_by-laws.
+    </p>
+    <p>
+        Since the success of this initiative is dependent on the involvement of
+        the very people it is meant to serve, we'd be very grateful if you were
+        to give due consideration to this proposal. We would expect you to
+        commit just 2-4 hours per month to help perform Editorial duties; we will
+        constantly adjust the number of Editorial Fellows to ensure this is the case. You
+        could try it out for 6 months or a year, and of course you could quit
+        any time you wished.
+    </p>
+    <p>
+        I'd be happy to provide you with more information, should you require
+        it. In view of our development plans, I would be grateful if you could
+        react (by replying to this email) within the next two or three weeks,
+        if possible. I'll be looking forward to your reaction, your comments
+        and suggestions, be they positive or negative. If you need more time
+        to consider, that's also fine; just let me know.
+    </p>
+    <p>
+        On behalf of the SciPost Foundation,<br>
+        <br>
+        <br>
+        <br>Prof. dr Jean-Sébastien Caux
+        <br>---------------------------------------------
+        <br>Institute for Theoretial Physics
+        <br>University of Amsterdam
+        <br>Science Park 904
+        <br>1098 XH Amsterdam
+        <br>The Netherlands
+        <br>---------------------------------------------
+        <br>tel.: +31 (0)20 5255775\nfax: +31 (0)20 5255778
+        <br>---------------------------------------------
+    </p>
+{% endif %}
+
+{% include 'email/_footer.html' %}
diff --git a/mails/templates/mail_templates/registration_invitation.json b/mails/templates/mail_templates/registration_invitation.json
new file mode 100644
index 0000000000000000000000000000000000000000..639804acfbc0fb392c5675b52a7a53fb42106189
--- /dev/null
+++ b/mails/templates/mail_templates/registration_invitation.json
@@ -0,0 +1,8 @@
+{
+    "subject": "SciPost: invitation",
+    "to_address": "email",
+    "bcc_to": "jscaux@scipost.org",
+    "from_address_name": "J-S Caux",
+    "from_address": "jscaux@scipost.org",
+    "context_object": "invitation"
+}
diff --git a/mails/templates/mail_templates/registration_invitation_reminder.html b/mails/templates/mail_templates/registration_invitation_reminder.html
new file mode 100644
index 0000000000000000000000000000000000000000..586e34be8c6596c7a31b793dfc74e116407c45dc
--- /dev/null
+++ b/mails/templates/mail_templates/registration_invitation_reminder.html
@@ -0,0 +1,4 @@
+<strong>Reminder: Invitation to SciPost</strong>
+<br>
+<br>
+{% include 'mail_templates/registration_invitation.html' %}
diff --git a/mails/templates/mail_templates/registration_invitation_reminder.json b/mails/templates/mail_templates/registration_invitation_reminder.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d280a9cf2705ca469019528cf3b0b0b0d4bd02c
--- /dev/null
+++ b/mails/templates/mail_templates/registration_invitation_reminder.json
@@ -0,0 +1,8 @@
+{
+    "subject": "RE: SciPost: invitation",
+    "to_address": "email",
+    "bcc_to": "jscaux@scipost.org",
+    "from_address_name": "J-S Caux",
+    "from_address": "jscaux@scipost.org",
+    "context_object": "invitation"
+}
diff --git a/mails/templates/mail_templates/registration_invitation_renewal.html b/mails/templates/mail_templates/registration_invitation_renewal.html
index 1456cda9998e5d04cd4325fbabe39009cde14a1a..c5c8e849c30d3f6f7d235ce0b237e364d5c2882d 100644
--- a/mails/templates/mail_templates/registration_invitation_renewal.html
+++ b/mails/templates/mail_templates/registration_invitation_renewal.html
@@ -274,7 +274,7 @@
     </p>
 
     <p>On behalf of the SciPost Foundation,
-        <br/>Prof. dr Jean-Sébastien Caux
+        <br/>Prof. dr Jean-Sébastien Caux
         <br/>---------------------------------------------
         <br/>Institute for Theoretial Physics
         <br/>University of Amsterdam
diff --git a/mails/templates/mails/mail_form.html b/mails/templates/mails/mail_form.html
index fbb170d4dcffc6286e03a3737fdc24d8eef863dc..4b4f9dd316e2268e89e46af6bad12d20ea79ded3 100644
--- a/mails/templates/mails/mail_form.html
+++ b/mails/templates/mails/mail_form.html
@@ -11,6 +11,7 @@
 
     <form enctype="multipart/form-data" method="post">
         {% csrf_token %}
+        {% if transfer_data_form %}{{ transfer_data_form }}{% endif %}
         {{ form|bootstrap }}
         <div class="form-group row">
             <div class="offset-md-2 col-md-10">
diff --git a/mails/utils.py b/mails/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee0d910d627e623f9c64064bb1894ce615aec123
--- /dev/null
+++ b/mails/utils.py
@@ -0,0 +1,14 @@
+from . import mixins
+
+
+class DirectMailUtil(mixins.MailUtilsMixin):
+    """
+    Same templates and json files as the form EmailTemplateForm, but this will directly send
+    the mails out, without intercepting and showing the mail editor to the user.
+    """
+
+    def __init__(self, mail_code, instance, *args, **kwargs):
+        kwargs['mail_code'] = mail_code
+        kwargs['instance'] = instance
+        super().__init__(*args, **kwargs)
+        self.validate()
diff --git a/mails/views.py b/mails/views.py
index f7fedc01ded99d81c6f90b0d33f17e8489024ca9..61e4e3ccc9b79ca7e5221092335e3df649aa853c 100644
--- a/mails/views.py
+++ b/mails/views.py
@@ -1,6 +1,6 @@
 from django.shortcuts import render
 
-from .forms import EmailTemplateForm
+from .forms import EmailTemplateForm, HiddenDataForm
 
 
 class MailEditingSubView(object):
@@ -14,6 +14,9 @@ class MailEditingSubView(object):
     def recipients_string(self):
         return ', '.join(getattr(self.mail_form, 'mail_fields', {}).get('recipients', ['']))
 
+    def add_form(self, form):
+        self.context['transfer_data_form'] = HiddenDataForm(form)
+
     def is_valid(self):
         return self.mail_form.is_valid()
 
diff --git a/scipost/admin.py b/scipost/admin.py
index 3b8059b73e28851d0ebc3e071091ae2b846fc0b2..c24d9cb4b428eaf0bcf0908c2a390185b1590c1b 100644
--- a/scipost/admin.py
+++ b/scipost/admin.py
@@ -5,12 +5,9 @@ from django.contrib.auth.admin import UserAdmin
 from django.contrib.auth.models import User, Permission
 
 from scipost.models import Contributor, Remark,\
-                           DraftInvitation,\
-                           RegistrationInvitation,\
                            AuthorshipClaim, PrecookedEmail,\
                            EditorialCollege, EditorialCollegeFellowship, UnavailabilityPeriod
 
-from journals.models import Publication
 from partners.admin import ContactToUserInline
 from production.admin import ProductionUserInline
 from submissions.models import Submission
@@ -122,49 +119,6 @@ class RemarkAdmin(admin.ModelAdmin):
 admin.site.register(Remark, RemarkAdmin)
 
 
-class DraftInvitationAdminForm(forms.ModelForm):
-    cited_in_submission = forms.ModelChoiceField(
-        required=False,
-        queryset=Submission.objects.order_by('-arxiv_identifier_w_vn_nr'))
-    cited_in_publication = forms.ModelChoiceField(
-        required=False,
-        queryset=Publication.objects.order_by('-publication_date'))
-
-    class Meta:
-        model = DraftInvitation
-        fields = '__all__'
-
-
-class DraftInvitationAdmin(admin.ModelAdmin):
-    search_fields = ['first_name', 'last_name', 'email', 'processed']
-    form = DraftInvitationAdminForm
-
-
-admin.site.register(DraftInvitation, DraftInvitationAdmin)
-
-
-class RegistrationInvitationAdminForm(forms.ModelForm):
-    cited_in_submission = forms.ModelChoiceField(
-        required=False,
-        queryset=Submission.objects.order_by('-arxiv_identifier_w_vn_nr'))
-    cited_in_publication = forms.ModelChoiceField(
-        required=False,
-        queryset=Publication.objects.order_by('-publication_date'))
-
-    class Meta:
-        model = RegistrationInvitation
-        fields = '__all__'
-
-
-class RegistrationInvitationAdmin(admin.ModelAdmin):
-    search_fields = ['first_name', 'last_name', 'email', 'invitation_key']
-    list_display = ['__str__', 'invitation_type', 'invited_by', 'responded']
-    list_filter = ['invitation_type', 'message_style', 'responded', 'declined']
-    date_hierarchy = 'date_sent'
-    form = RegistrationInvitationAdminForm
-
-
-admin.site.register(RegistrationInvitation, RegistrationInvitationAdmin)
 admin.site.register(AuthorshipClaim)
 admin.site.register(Permission)
 
diff --git a/scipost/forms.py b/scipost/forms.py
index 60c748382e617c1bc3a971b3caefdddb3ebb3f63..9f68b07836b683bf59804cdf3fb3e37647dd66d0 100644
--- a/scipost/forms.py
+++ b/scipost/forms.py
@@ -19,10 +19,9 @@ from captcha.fields import ReCaptchaField
 from ajax_select.fields import AutoCompleteSelectField
 from haystack.forms import ModelSearchForm as HayStackSearchForm
 
-from .constants import SCIPOST_DISCIPLINES, TITLE_CHOICES, SCIPOST_FROM_ADDRESSES,\
-    INVITATION_CITED_SUBMISSION, INVITATION_CITED_PUBLICATION
+from .constants import SCIPOST_DISCIPLINES, TITLE_CHOICES, SCIPOST_FROM_ADDRESSES
 from .decorators import has_contributor
-from .models import Contributor, DraftInvitation, RegistrationInvitation,\
+from .models import Contributor, DraftInvitation,\
                     UnavailabilityPeriod, PrecookedEmail
 
 from affiliations.models import Affiliation, Institution
@@ -145,8 +144,7 @@ class DraftInvitationForm(forms.ModelForm):
         model = DraftInvitation
         fields = ['title', 'first_name', 'last_name', 'email',
                   'invitation_type',
-                  'cited_in_submission', 'cited_in_publication'
-                  ]
+                  'cited_in_submission', 'cited_in_publication']
 
     def __init__(self, *args, **kwargs):
         '''
@@ -161,19 +159,14 @@ class DraftInvitationForm(forms.ModelForm):
         if self.instance.id:
             return email
 
-        if RegistrationInvitation.objects.filter(email=email).exists():
-            self.add_error('email', 'This email address has already been used for an invitation')
-        elif DraftInvitation.objects.filter(email=email).exists():
-            self.add_error('email', ('This email address has already been'
-                                     ' used for a draft invitation'))
-        elif User.objects.filter(email=email).exists():
+        if User.objects.filter(email=email).exists():
             self.add_error('email', 'This email address is already associated to a Contributor')
 
         return email
 
     def clean_invitation_type(self):
         invitation_type = self.cleaned_data['invitation_type']
-        if invitation_type == 'F' and not self.current_user.has_perm('scipost.can_invite_Fellows'):
+        if invitation_type == 'F' and not self.current_user.has_perm('scipost.can_invite_fellows'):
             self.add_error('invitation_type', ('You do not have the authorization'
                                                ' to send a Fellow-type invitation.'
                                                ' Consider Contributor, or cited (sub/pub).'))
@@ -184,95 +177,6 @@ class DraftInvitationForm(forms.ModelForm):
         return invitation_type
 
 
-class ContributorsFilterForm(forms.Form):
-    names = forms.CharField(widget=forms.Textarea())
-    include_invitations = forms.BooleanField(required=False, initial=True,
-                                             label='Include invitations in the filter.')
-
-    def filter(self):
-        names_found = []
-        names_not_found = []
-        invitations_found = []
-        r = self.cleaned_data['names'].replace('\r', '\n').split('\n')
-        include_invitations = self.cleaned_data.get('include_invitations', False)
-        for name in r:
-            last_name = name.split(',')[0]
-            if not last_name:
-                continue
-            if Contributor.objects.filter(user__last_name__istartswith=last_name).exists():
-                names_found.append(name)
-            elif include_invitations and RegistrationInvitation.objects.pending_response().filter(
-              last_name__istartswith=last_name).exists():
-                invitations_found.append(name)
-            else:
-                names_not_found.append(name)
-        return names_found, names_not_found, invitations_found
-
-
-class RegistrationInvitationForm(forms.ModelForm):
-    cited_in_submission = AutoCompleteSelectField('submissions_lookup', required=False)
-    cited_in_publication = AutoCompleteSelectField('publication_lookup', required=False)
-
-    class Meta:
-        model = RegistrationInvitation
-        fields = ['title', 'first_name', 'last_name', 'email',
-                  'invitation_type',
-                  'cited_in_submission', 'cited_in_publication',
-                  'message_style', 'personal_message'
-                  ]
-
-    def __init__(self, *args, **kwargs):
-        '''
-        This form has a required keyword argument `current_user` which is used for validation of
-        the form fields.
-        '''
-        self.current_user = kwargs.pop('current_user')
-        if kwargs.get('initial', {}).get('cited_in_submission', False):
-            kwargs['initial']['cited_in_submission'] = kwargs['initial']['cited_in_submission'].id
-        if kwargs.get('initial', {}).get('cited_in_publication', False):
-            kwargs['initial']['cited_in_publication'] = kwargs['initial']['cited_in_publication'].id
-
-        super().__init__(*args, **kwargs)
-        self.fields['personal_message'].widget.attrs.update(
-            {'placeholder': ('NOTE: a personal phrase or two.'
-                             ' The bulk of the text will be auto-generated.')})
-
-        self.fields['cited_in_publication'] = forms.ModelChoiceField(
-            queryset=Publication.objects.all().order_by('-publication_date'),
-            required=False)
-
-    def clean(self):
-        data = self.cleaned_data
-        if data.get('invitation_type') == INVITATION_CITED_SUBMISSION:
-            if not data.get('cited_in_submission'):
-                self.add_error('cited_in_submission', 'Please state the Submission cited.')
-        if data.get('invitation_type') == INVITATION_CITED_PUBLICATION:
-            if not data.get('cited_in_publication'):
-                self.add_error('cited_in_publication', 'Please state the Publication cited.')
-        return data
-
-    def clean_email(self):
-        email = self.cleaned_data['email']
-        if RegistrationInvitation.objects.filter(email=email).exists():
-            self.add_error('email', 'This email address has already been used for an invitation')
-        elif User.objects.filter(email=email).exists():
-            self.add_error('email', 'This email address is already associated to a Contributor')
-
-        return email
-
-    def clean_invitation_type(self):
-        invitation_type = self.cleaned_data['invitation_type']
-        if invitation_type == 'F' and not self.current_user.has_perm('scipost.can_invite_Fellows'):
-            self.add_error('invitation_type', ('You do not have the authorization'
-                                               ' to send a Fellow-type invitation.'
-                                               ' Consider Contributor, or cited (sub/pub).'))
-        if invitation_type == 'R':
-            self.add_error('invitation_type', ('Referee-type invitations must be made by the'
-                                               ' Editor-in-charge at the relevant Submission'
-                                               '\'s Editorial Page. '))
-        return invitation_type
-
-
 class ModifyPersonalMessageForm(forms.Form):
     personal_message = forms.CharField(widget=forms.Textarea())
 
diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py
index 36a786563117ac25975e17bc3520a63a35a04084..38cb301326cb85344b42192f17ec18983356bcd4 100644
--- a/scipost/management/commands/add_groups_and_permissions.py
+++ b/scipost/management/commands/add_groups_and_permissions.py
@@ -66,27 +66,20 @@ class Command(BaseCommand):
             content_type=content_type)
 
         # Registration and invitations
-        change_draft_invitation, created = Permission.objects.get_or_create(
-            codename='change_draftinvitation',
-            defaults={
-                'name': 'Can vet registration requests',
-                'content_type': content_type_draft_invitation
-            }
-        )
         can_vet_registration_requests, created = Permission.objects.get_or_create(
             codename='can_vet_registration_requests',
             name='Can vet registration requests',
             content_type=content_type)
-        can_draft_registration_invitations, created = Permission.objects.get_or_create(
-            codename='can_draft_registration_invitations',
-            name='Can draft registration invitations',
+        can_create_registration_invitations, created = Permission.objects.get_or_create(
+            codename='can_create_registration_invitations',
+            name='Can create registration invitations',
             content_type=content_type)
         can_manage_registration_invitations, created = Permission.objects.get_or_create(
             codename='can_manage_registration_invitations',
             name='Can manage registration invitations',
             content_type=content_type)
-        can_invite_Fellows, created = Permission.objects.get_or_create(
-            codename='can_invite_Fellows',
+        can_invite_fellows, created = Permission.objects.get_or_create(
+            codename='can_invite_fellows',
             name='Can invite Fellows',
             content_type=content_type)
         can_resend_registration_requests, created = Permission.objects.get_or_create(
@@ -284,7 +277,7 @@ class Command(BaseCommand):
         SciPostAdmin.permissions.set([
             can_read_all_privacy_sensitive_data,
             can_manage_registration_invitations,
-            change_draft_invitation,
+            can_create_registration_invitations
             can_email_group_members,
             can_email_particulars,
             can_resend_registration_requests,
@@ -315,14 +308,14 @@ class Command(BaseCommand):
 
         AdvisoryBoard.permissions.set([
             can_manage_registration_invitations,
-            change_draft_invitation,
+            can_create_registration_invitations
             can_attend_VGMs,
             can_view_statistics,
         ])
 
         EditorialAdmin.permissions.set([
             can_view_pool,
-            can_invite_Fellows,
+            can_invite_fellows,
             can_assign_submissions,
             can_do_plagiarism_checks,
             can_oversee_refereeing,
@@ -372,12 +365,12 @@ class Command(BaseCommand):
         ])
 
         Ambassadors.permissions.set([
+            can_create_registration_invitations,
             can_manage_registration_invitations,
-            change_draft_invitation,
         ])
 
         JuniorAmbassadors.permissions.set([
-            can_draft_registration_invitations,
+            can_create_registration_invitations,
         ])
 
         ProductionSupervisors.permissions.set([
diff --git a/scipost/managers.py b/scipost/managers.py
index db457fd9bd20d2ea05fafc34f92021b5f50d5740..9233bc9be2ea6f0bf4840ce9410658b46f4019ad 100644
--- a/scipost/managers.py
+++ b/scipost/managers.py
@@ -2,8 +2,7 @@ from django.db import models
 from django.db.models import Q
 from django.utils import timezone
 
-from .constants import CONTRIBUTOR_NORMAL, INVITATION_EDITORIAL_FELLOW,\
-                       CONTRIBUTOR_NEWLY_REGISTERED, AUTHORSHIP_CLAIM_PENDING
+from .constants import CONTRIBUTOR_NORMAL, CONTRIBUTOR_NEWLY_REGISTERED, AUTHORSHIP_CLAIM_PENDING
 
 today = timezone.now().date()
 
@@ -37,25 +36,6 @@ class ContributorQuerySet(models.QuerySet):
         return self.filter(user__groups__name='Editorial College')
 
 
-class RegistrationInvitationManager(models.Manager):
-    def pending_invited_fellows(self):
-        return self.filter(invitation_type=INVITATION_EDITORIAL_FELLOW,
-                           responded=False, declined=False)
-
-    def declined_invited_fellows(self):
-        return self.filter(invitation_type=INVITATION_EDITORIAL_FELLOW,
-                           responded=False, declined=True)
-
-    def declined(self):
-        return self.filter(responded=True, declined=True)
-
-    def pending_response(self):
-        return self.filter(responded=False)
-
-    def declined_or_without_response(self):
-        return self.filter(Q(responded=True, declined=True) | Q(responded=False))
-
-
 class UnavailabilityPeriodManager(models.Manager):
     def today(self):
         return self.filter(start__lte=today, end__gte=today)
diff --git a/scipost/migrations/0005_auto_20180218_1556.py b/scipost/migrations/0005_auto_20180218_1556.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ac3f68be6966a91551ed637d0e0bd7f91bce982
--- /dev/null
+++ b/scipost/migrations/0005_auto_20180218_1556.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-18 14:56
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0004_auto_20180212_1932'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='registrationinvitation',
+            name='first_name',
+            field=models.CharField(max_length=30),
+        ),
+        migrations.AlterField(
+            model_name='registrationinvitation',
+            name='last_name',
+            field=models.CharField(max_length=30),
+        ),
+    ]
diff --git a/scipost/models.py b/scipost/models.py
index b217d78e78b1c207bbb7cb6aab19ae709dc0438d..46e448359e087de28e4b7c138255143d56f45354 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -18,7 +18,7 @@ from .constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS,\
                        AUTHORSHIP_CLAIM_PENDING, AUTHORSHIP_CLAIM_STATUS,\
                        CONTRIBUTOR_NEWLY_REGISTERED
 from .fields import ChoiceArrayField
-from .managers import FellowManager, ContributorQuerySet, RegistrationInvitationManager,\
+from .managers import FellowManager, ContributorQuerySet,\
                       UnavailabilityPeriodManager, AuthorshipClaimQuerySet
 
 today = timezone.now().date()
@@ -189,11 +189,11 @@ class DraftInvitation(models.Model):
 
 class RegistrationInvitation(models.Model):
     """
-    Invitation to particular persons for registration
+    Deprecated: Use the `invitations` app
     """
     title = models.CharField(max_length=4, choices=TITLE_CHOICES)
-    first_name = models.CharField(max_length=30, default='')
-    last_name = models.CharField(max_length=30, default='')
+    first_name = models.CharField(max_length=30)
+    last_name = models.CharField(max_length=30)
     email = models.EmailField()
     invitation_type = models.CharField(max_length=2, choices=INVITATION_TYPE,
                                        default=INVITATION_CONTRIBUTOR)
@@ -210,7 +210,7 @@ class RegistrationInvitation(models.Model):
     invitation_key = models.CharField(max_length=40, unique=True)
     key_expires = models.DateTimeField(default=timezone.now)
     date_sent = models.DateTimeField(default=timezone.now)
-    invited_by = models.ForeignKey(Contributor,
+    invited_by = models.ForeignKey('scipost.Contributor',
                                    on_delete=models.CASCADE,
                                    blank=True, null=True)
     nr_reminders = models.PositiveSmallIntegerField(default=0)
@@ -218,29 +218,11 @@ class RegistrationInvitation(models.Model):
     responded = models.BooleanField(default=False)
     declined = models.BooleanField(default=False)
 
-    objects = RegistrationInvitationManager()
-
-    class Meta:
-        ordering = ['last_name']
-
-    def __str__(self):
-        return (self.first_name + ' ' + self.last_name
-                + ' on ' + self.date_sent.strftime("%Y-%m-%d"))
-
-    def refresh_keys(self, force_new_key=False):
-        # Generate email activation key and link
-        if not self.invitation_key or force_new_key:
-            salt = ""
-            for i in range(5):
-                salt = salt + random.choice(string.ascii_letters)
-            salt = salt.encode('utf8')
-            invitationsalt = self.last_name.encode('utf8')
-            self.invitation_key = hashlib.sha1(salt + invitationsalt).hexdigest()
-        self.key_expires = timezone.now() + datetime.timedelta(days=365)
-        self.save()
-
 
 class CitationNotification(models.Model):
+    """
+    Deprecated: Use the `invitations` app
+    """
     contributor = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE)
     cited_in_submission = models.ForeignKey('submissions.Submission',
                                             on_delete=models.CASCADE,
@@ -250,16 +232,6 @@ class CitationNotification(models.Model):
                                              blank=True, null=True)
     processed = models.BooleanField(default=False)
 
-    def __str__(self):
-        text = str(self.contributor) + ', cited in '
-        if self.cited_in_submission:
-            text += self.cited_in_submission.arxiv_identifier_w_vn_nr
-        elif self.cited_in_publication:
-            text += self.cited_in_publication.citation()
-        if self.processed:
-            text += ' (processed)'
-        return text
-
 
 class AuthorshipClaim(models.Model):
     claimant = models.ForeignKey('scipost.Contributor',
diff --git a/scipost/static/scipost/assets/css/_buttons.scss b/scipost/static/scipost/assets/css/_buttons.scss
index 6abdc7c02b81ac4c4a9c4d8ce9cc82c87f0ef977..7a7b57a4ec67b3e60f7432b80d8e0bebc8b3362d 100644
--- a/scipost/static/scipost/assets/css/_buttons.scss
+++ b/scipost/static/scipost/assets/css/_buttons.scss
@@ -12,6 +12,10 @@
     }
 }
 
+.btn-link {
+    color: $scipost-lightblue;
+}
+
 .btn-secondary {
     color: $scipost-darkblue;
     background-color: $white;
diff --git a/scipost/static/scipost/assets/css/_form.scss b/scipost/static/scipost/assets/css/_form.scss
index e0182d0420dcb8d74eccf2521dc7e72103560f7e..27a45b99ee238df2992b68646487ce2b4e30a9ec 100644
--- a/scipost/static/scipost/assets/css/_form.scss
+++ b/scipost/static/scipost/assets/css/_form.scss
@@ -117,3 +117,24 @@ select.form-control {
     height: auto;
   }
 }
+
+
+// Autocomplete fields
+.results_on_deck {
+    .help-block {
+        color: #666;
+        font-style: italic;
+    }
+
+    > div {
+        background-color: $scipost-lightestblue;
+        padding: 2px 10px;
+        display: inline-block;
+        border-radius: 5px;
+
+        .ui-icon {
+            margin-top: 0;
+            margin-right: 3px;
+        }
+    }
+}
diff --git a/scipost/static/scipost/assets/css/_tables.scss b/scipost/static/scipost/assets/css/_tables.scss
index 8873b93c394e15c1ef958f4509818a1f193e5c59..7c3d0fdc6c1d930fd04287fd8b90807a6e4410b8 100644
--- a/scipost/static/scipost/assets/css/_tables.scss
+++ b/scipost/static/scipost/assets/css/_tables.scss
@@ -12,6 +12,8 @@
 }
 
 table.commentary td,
+table.registration_invitation td,
+table.registration_invitation th,
 table.submission td {
     padding: 0.1em 0.7em;
 
diff --git a/scipost/templates/partials/scipost/personal_page/editorial_actions.html b/scipost/templates/partials/scipost/personal_page/editorial_actions.html
index b8fc99a5ff64bcbe90296a7f436111cbd2c5e4cb..0996ec371eb0ca3527c2fc3deb66cf7d6e3e089a 100644
--- a/scipost/templates/partials/scipost/personal_page/editorial_actions.html
+++ b/scipost/templates/partials/scipost/personal_page/editorial_actions.html
@@ -10,7 +10,7 @@
 </div>
 
 <div class="row">
-    {% if perms.scipost.can_vet_registration_requests or perms.scipost.can_draft_registration_invitations or perms.scipost.can_manage_registration_invitations %}
+    {% if perms.scipost.can_vet_registration_requests or perms.scipost.can_create_registration_invitations or perms.scipost.can_resend_registration_requests %}
     <div class="col-md-4">
         <h3>Registration actions</h3>
         <ul>
@@ -20,19 +20,15 @@
             {% if perms.scipost.can_resend_registration_requests %}
                 <li><a href="{% url 'scipost:registration_requests' %}">Awaiting validation</a> ({{ nr_reg_awaiting_validation }})</li>
             {% endif %}
-            {% if perms.scipost.can_draft_registration_invitations %}
-                <li><a href="{% url 'scipost:contributors_filter' %}">Contributors filter</a></li>
-                <li><a href="{% url 'scipost:draft_registration_invitation' %}">Draft a Registration Invitation</a></li>
-            {% endif %}
-            {% if perms.scipost.can_manage_registration_invitations %}
-                <li><a href="{% url 'scipost:registration_invitations' %}">Manage Registration Invitations</a></li>
+            {% if perms.scipost.can_create_registration_invitations %}
+                <li><a href="{% url 'invitations:list' %}">Manage Registration Invitations</a></li>
             {% endif %}
         </ul>
 
         {% if perms.scipost.can_manage_registration_invitations %}
             <h3>Notifications</h3>
             <ul>
-                <li><a href="{% url 'scipost:citation_notifications' %}">Manage citation notifications</a></li>
+                <li><a href="{% url 'invitations:citation_notification_list' %}">Manage citation notifications</a></li>
             </ul>
         {% endif %}
 
diff --git a/scipost/templates/scipost/_draft_registration_tables.html b/scipost/templates/scipost/_draft_registration_tables.html
deleted file mode 100644
index 76765d3f3e4b824531631a0ed6349fc29c29c4aa..0000000000000000000000000000000000000000
--- a/scipost/templates/scipost/_draft_registration_tables.html
+++ /dev/null
@@ -1,425 +0,0 @@
-
-<h2 class="highlight">Invitations sent (response pending)</h2>
-
-<h3>Editorial Fellows ({{sent_reg_inv_fellows|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_sent_reg_inv_fellows">view/hide +</a>
-
-<table class="table" id="table_sent_reg_inv_fellows" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-            <th></th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for fellow in sent_reg_inv_fellows %}
-          <tr>
-            <td>{{ fellow.last_name }}</td>
-            <td>{{ fellow.first_name }}</td>
-	    <td>{{ fellow.get_title_display }}</td>
-            <td>{{ fellow.email }}</td>
-            <td>{{ fellow.date_sent }} </td>
-            <td>{{ fellow.get_invitation_type_display }}</td>
-            <td>{{ fellow.invited_by.user.first_name }} {{ fellow.invited_by.user.last_name }}</td>
-            <td>
-                {% if perms.scipost.can_invite_Fellows %}
-                    <a href="{% url 'scipost:renew_registration_invitation' invitation_id=fellow.id %}">Renew</a> ({{ fellow.nr_reminders }}) {% if fellow.date_last_reminded %}(last: {{ fellow.date_last_reminded|date:"Y-m-d" }}){% endif %}
-                    &middot;
-                    <a href="{% url 'scipost:mark_reg_inv_as_declined' invitation_id=fellow.id %}">Declined</a>
-                {% endif %}
-            </td>
-
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="7">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-<h3>Normal Contributors ({{sent_reg_inv_contrib|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_sent_reg_inv_contrib">view/hide +</a>
-
-<table class="table" id="table_sent_reg_inv_contrib" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-            <th></th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in sent_reg_inv_contrib %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-            <td>
-                {% if perms.scipost.can_invite_Fellows %}
-                    <a href="{% url 'scipost:renew_registration_invitation' invitation_id=invitation.id %}">Renew</a> ({{ invitation.nr_reminders }}) {% if invitation.date_last_reminded %}(last: {{ invitation.date_last_reminded|date:"Y-m-d" }}){% endif %}
-                    &middot;
-                    <a href="{% url 'scipost:mark_reg_inv_as_declined' invitation_id=invitation.id %}">Declined</a>
-                {% endif %}
-            </td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="7">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-
-<h3>Referees ({{sent_reg_inv_ref|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_sent_reg_inv_ref">view/hide +</a>
-
-<table class="table" id="table_sent_reg_inv_ref" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-            <th></th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in sent_reg_inv_ref %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-            <td>
-                {% if perms.scipost.can_invite_Fellows %}
-                    <a href="{% url 'scipost:renew_registration_invitation' invitation_id=invitation.id %}">Renew</a> ({{ invitation.nr_reminders }}) {% if invitation.date_last_reminded %}(last: {{ invitation.date_last_reminded|date:"Y-m-d" }}){% endif %}
-                {% endif %}
-            </td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="7">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-<h3>Cited in sub ({{sent_reg_inv_cited_sub|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_sent_reg_inv_cited_sub">view/hide +</a>
-
-<table class="table" id="table_sent_reg_inv_cited_sub" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-            <th></th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in sent_reg_inv_cited_sub %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-            <td>
-                {% if perms.scipost.can_invite_Fellows %}
-                    <a href="{% url 'scipost:renew_registration_invitation' invitation_id=invitation.id %}">Renew</a> ({{ invitation.nr_reminders }}) {% if invitation.date_last_reminded %}(last: {{ invitation.date_last_reminded|date:"Y-m-d" }}){% endif %}
-                {% endif %}
-            </td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="7">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-
-<h3>Cited in pub ({{sent_reg_inv_cited_pub|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_sent_reg_inv_cited_pub">view/hide +</a>
-
-<table class="table" id="table_sent_reg_inv_cited_pub" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-            <th></th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in sent_reg_inv_cited_pub %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-            <td>
-                {% if perms.scipost.can_invite_Fellows %}
-                    <a href="{% url 'scipost:renew_registration_invitation' invitation_id=invitation.id %}">Renew</a> ({{ invitation.nr_reminders }}) {% if invitation.date_last_reminded %}(last: {{ invitation.date_last_reminded|date:"Y-m-d" }}){% endif %}
-                {% endif %}
-            </td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="7">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-<h2 class="highlight">Invitations sent (responded)</h2>
-
-<h3>Editorial Fellows ({{resp_reg_inv_fellow|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_resp_reg_inv_fellow">view/hide +</a>
-
-<table class="table" id="table_resp_reg_inv_fellow" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in resp_reg_inv_fellow %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="6">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-<h3>Normal Contributors ({{resp_reg_inv_contrib|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_resp_reg_inv_contrib">view/hide +</a>
-
-<table class="table" id="table_resp_reg_inv_contrib" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in resp_reg_inv_contrib %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="6">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-<h3>Referees ({{resp_reg_inv_ref|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_resp_reg_inv_ref">view/hide +</a>
-
-<table class="table" id="table_resp_reg_inv_ref" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in resp_reg_inv_ref %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="6">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-<h3>Cited in sub ({{resp_reg_inv_cited_sub|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_resp_reg_inv_cited_sub">view/hide +</a>
-
-<table class="table" id="table_resp_reg_inv_cited_sub" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in resp_reg_inv_cited_sub %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="6">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-<h3>Cited in pub ({{resp_reg_inv_cited_pub|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_resp_reg_inv_cited_pub">view/hide +</a>
-
-<table class="table" id="table_resp_reg_inv_cited_pub" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in resp_reg_inv_cited_pub %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="6">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-<h3>Declined ({{decl_reg_inv|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_decl_reg_inv">view/hide +</a>
-
-<table class="table" id="table_decl_reg_inv" style="display: none;">
-    <thead>
-        <tr>
-            <th>Last name</th>
-            <th>First name</th>
-	    <th>Title</th>
-            <th>Email</th>
-            <th>Date sent</th>
-            <th>Type</th>
-            <th>Invited by</th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for invitation in decl_reg_inv %}
-          <tr>
-            <td>{{ invitation.last_name }}</td>
-            <td>{{ invitation.first_name }}</td>
-	    <td>{{ invitation.get_title_display }}</td>
-            <td>{{ invitation.email }}</td>
-            <td>{{ invitation.date_sent }} </td>
-            <td>{{ invitation.get_invitation_type_display }}</td>
-            <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td>
-          </tr>
-        {% empty %}
-            <tr>
-                <td colspan="6">No invitations found.</td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>
-
-<h2 class="highlight">List of already-registered contributors ({{names_reg_contributors|length}})</h3>
-<a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#registered_contributors">view/hide +</a>
-
-<div class="card-columns" id="registered_contributors" style="display: none;">
-    {% for first_name, last_name in names_reg_contributors %}
-        <div class="card border-0">
-          {{ last_name }}, {{ first_name }}
-        </div>
-    {% endfor %}
-</div>
diff --git a/scipost/templates/scipost/citation_notifications.html b/scipost/templates/scipost/citation_notifications.html
deleted file mode 100644
index 49c6616840c6dd543d95e78c5b4fa3cb724c30a0..0000000000000000000000000000000000000000
--- a/scipost/templates/scipost/citation_notifications.html
+++ /dev/null
@@ -1,36 +0,0 @@
-{% extends 'scipost/_personal_page_base.html' %}
-
-{% block pagetitle %}: citation notifications{% endblock pagetitle %}
-
-{% block breadcrumb_items %}
-    {{block.super}}
-    <span class="breadcrumb-item">Pool</span>
-{% endblock %}
-
-{% block content %}
-
-
-<div class="row">
-    <div class="col-12">
-        <h1 class="highlight">Citation notifications to process</h1>
-        {% if errormessage %}
-            <h3 class="text-danger">{{ errormessage }}</h3>
-        {% endif %}
-
-        <ul>
-          {% for un in unprocessed_notifications %}
-              <li>
-                  {{ un }} <a href="{% url 'scipost:process_citation_notification' cn_id=un.id %}">Process this notification</a>
-              </li>
-          {% empty %}
-              <li>There are no citation notifications to process.</li>
-          {% endfor %}
-        </ul>
-
-        <p>Return to your <a href="{% url 'scipost:personal_page' %}">personal page</a>.</p>
-    </div>
-</div>
-
-
-
-{% endblock content %}
diff --git a/scipost/templates/scipost/contributors_filter.html b/scipost/templates/scipost/contributors_filter.html
deleted file mode 100644
index 12418bd8a252a1be98829df8c4ee790cd806ce2a..0000000000000000000000000000000000000000
--- a/scipost/templates/scipost/contributors_filter.html
+++ /dev/null
@@ -1,50 +0,0 @@
-{% extends 'scipost/_personal_page_base.html' %}
-
-{% load bootstrap %}
-
-{% block pagetitle %}: contributors filter{% endblock pagetitle %}
-
-{% block breadcrumb_items %}
-    {{block.super}}
-    <a href="{% url 'scipost:draft_registration_invitation' %}" class="breadcrumb-item">Draft registration invitation</a>
-    <span class="breadcrumb-item">Contributors filter</span>
-{% endblock %}
-
-{% block content %}
-
-<div class="row">
-    <div class="col-12">
-        <h1 class="highlight">Contributors filter</h1>
-
-        <p>This form can be used to split your list of names into a list of names with registered or already invited Contributors and a list of unknown names according to the current database.</p>
-        <p>Please, for every name use the format <code>{last name}</code> or  <code>{last name}, {first name}</code> and use one name per line.</p>
-
-        <form method="post">
-              {% csrf_token %}
-              {{ form|bootstrap }}
-              <input type="submit" class="btn btn-primary" value="Filter"/>
-        </form>
-    </div>
-</div>
-
-{% if form.is_bound %}
-
-    <hr class="divider">
-    <h2>Filter result</h2>
-    {% if names_not_found %}
-        <h3>New names</h3>
-        <pre class="mb-3"><code>{% for name in names_not_found %}{{ name }}{% if not forloop.last %}<br>{% endif %}{% endfor %}</code></pre>
-    {% endif %}
-
-    {% if names_found %}
-        <h3>Names found in the system</h3>
-        <pre class="mb-3"><code>{% for name in names_found %}{{ name }}{% if not forloop.last %}<br>{% endif %}{% endfor %}</code></pre>
-    {% endif %}
-
-    {% if invitations_found %}
-        <h3>Invitations (pending response) found in database</h3>
-        <pre class="mb-3"><code>{% for name in invitations_found %}{{ name }}{% if not forloop.last %}<br>{% endif %}{% endfor %}</code></pre>
-    {% endif %}
-{% endif %}
-
-{% endblock %}
diff --git a/scipost/templates/scipost/draft_registration_invitation.html b/scipost/templates/scipost/draft_registration_invitation.html
deleted file mode 100644
index 4f8900f25de969cfeed59c63bbccb061a33c4c6d..0000000000000000000000000000000000000000
--- a/scipost/templates/scipost/draft_registration_invitation.html
+++ /dev/null
@@ -1,99 +0,0 @@
-{% extends 'scipost/_personal_page_base.html' %}
-
-{% load bootstrap %}
-
-{% block pagetitle %}: registration invitations{% endblock pagetitle %}
-
-{% block breadcrumb_items %}
-    {{block.super}}
-    <span class="breadcrumb-item">Draft registration invitation</span>
-{% endblock %}
-
-{% block content %}
-
-<script>
-$(document).ready(function(){
-
-    $('#id_invitation_type').on('change', function() {
-        switch ($(this).val()) {
-            case "ci":
-                $("#id_cited_in_submission").parents('.form-group').show();
-                $("#id_cited_in_publication").parents('.form-group').hide();
-            break;
-            case "cp":
-                $("#id_cited_in_submission").parents('.form-group').hide();
-                $("#id_cited_in_publication").parents('.form-group').show();
-            break;
-            default:
-                $("#id_cited_in_submission").parents('.form-group').hide();
-                $("#id_cited_in_publication").parents('.form-group').hide();
-        }
-    }).trigger('change');
-});
-</script>
-
-<div class="row">
-    <div class="col-12">
-        <h1 class="highlight">Draft a registration invitation</h1>
-        <p>If you have a list of names you want to check with the current database of users, <a href="{% url 'scipost:contributors_filter' %}">please click here</a>.</p>
-    </div>
-</div>
-
-<div class="row">
-    <div class="col-12">
-        <h2 class="highlight">Draft a new invitation</h2>
-        {% if errormessage %}
-            <h3 class="text-danger">{{ errormessage }}</h3>
-        {% endif %}
-
-        <form action="{% url 'scipost:draft_registration_invitation' %}" method="post">
-              {% csrf_token %}
-              {{ form.media }}
-              {{ form|bootstrap }}
-              <input type="submit" class="btn btn-primary" value="Submit"/>
-        </form>
-    </div>
-</div>
-
-<div class="row">
-    <div class="col-12">
-        <h2 class="highlight">Existing drafts (to be processed by Admin) ({{existing_drafts|length}})</h2>
-        <a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_existing_drafts">view/hide +</a>
-
-        <table class="table" id="table_existing_drafts">
-            <thead>
-                <tr>
-                    <th>Last name</th>
-                    <th>First name</th>
-                    <th>Email</th>
-                    <th>Date drafted</th>
-                    <th>Type</th>
-                    <th>Drafted by</th>
-                    <th></th>
-                </tr>
-            </thead>
-            <tbody>
-                {% for draft in existing_drafts %}
-                  <tr>
-                    <td>{{ draft.last_name }}</td>
-                    <td>{{ draft.first_name }}</td>
-                    <td>{{ draft.email }}</td>
-                    <td>{{ draft.date_drafted }} </td>
-                    <td>{{ draft.get_invitation_type_display }}</td>
-                    <td>{{ draft.drafted_by.user.first_name }} {{ draft.drafted_by.user.last_name }}</td>
-                    <td>
-                        {% if draft.drafted_by.user == request.user %}
-                            <a href="{% url 'scipost:edit_draft_reg_inv' draft.id %}">Edit</a>
-                        {% endif %}</td>
-                  </tr>
-                {% empty %}
-                    <tr>
-                        <td colspan="7">No drafts found.</td>
-                    </tr>
-                {% endfor %}
-            </tbody>
-        </table>
-    </div>
-</div>
-
-{% endblock %}
diff --git a/scipost/templates/scipost/edit_draft_reg_inv.html b/scipost/templates/scipost/edit_draft_reg_inv.html
deleted file mode 100644
index f6fd6afe34f7e90492f7e502306e8cc1e280bc1f..0000000000000000000000000000000000000000
--- a/scipost/templates/scipost/edit_draft_reg_inv.html
+++ /dev/null
@@ -1,48 +0,0 @@
-{% extends 'scipost/_personal_page_base.html' %}
-
-{% block pagetitle %}: edit draft reg inv{% endblock pagetitle %}
-
-{% block breadcrumb_items %}
-    {{block.super}}
-    <a href="{% url 'scipost:registration_invitations' %}" class="breadcrumb-item">Registration Invitations</a>
-    <span class="breadcrumb-item">Pool</span>
-{% endblock %}
-
-{% load bootstrap %}
-
-{% block content %}
-
-<script>
-$(document).ready(function(){
-
-    $('#id_invitation_type').on('change', function() {
-        switch ($(this).val()) {
-            case "ci":
-                $("#id_cited_in_submission").parents('.form-group').show();
-                $("#id_cited_in_publication").parents('.form-group').hide();
-            break;
-            case "cp":
-                $("#id_cited_in_submission").parents('.form-group').hide();
-                $("#id_cited_in_publication").parents('.form-group').show();
-            break;
-            default:
-                $("#id_cited_in_submission").parents('.form-group').hide();
-                $("#id_cited_in_publication").parents('.form-group').hide();
-        }
-    }).trigger('change');
-});
-</script>
-
-<div class="row">
-    <div class="col-12">
-        <h1 class="highlight">Edit a draft registration invitation</h1>
-        <form action="{% url 'scipost:edit_draft_reg_inv' draft_id=draft_inv_form.instance.id %}" method="post">
-            {% csrf_token %}
-            {{draft_inv_form.media}}
-            {{draft_inv_form|bootstrap}}
-            <input type="submit" class="btn btn-secondary">
-        </form>
-    </div>
-</div>
-
-{% endblock content %}
diff --git a/scipost/templates/scipost/edit_invitation_personal_message.html b/scipost/templates/scipost/edit_invitation_personal_message.html
deleted file mode 100644
index 5a63e60729afb5e3f300043644f926ab7cfbb2e7..0000000000000000000000000000000000000000
--- a/scipost/templates/scipost/edit_invitation_personal_message.html
+++ /dev/null
@@ -1,21 +0,0 @@
-{% extends 'scipost/base.html' %}
-
-{% load scipost_extras %}
-{% load bootstrap %}
-
-{% block pagetitle %}: registration invitation: edit personal message{% endblock pagetitle %}
-
-{% block content %}
-
-<h2>Edit invitation's personal message: for {{ invitation.first_name }} {{ invitation.last_name }}</h2>
-
-{% if errormessage %}
-    <h3 class="text-danger">{{ errormessage }}</h3>
-{% endif %}
-<form action="{% url 'scipost:edit_invitation_personal_message' invitation_id=invitation.id %}" method="post">
-    {% csrf_token %}
-    {{ form|bootstrap }}
-    <input type="submit" value="Submit" class="btn btn-primary">
-</form>
-
-{% endblock %}
diff --git a/scipost/templates/scipost/registration_invitation_sent.html b/scipost/templates/scipost/registration_invitation_sent.html
deleted file mode 100644
index f2908e563f0affa74ab03d291cfa0f62a1d13e15..0000000000000000000000000000000000000000
--- a/scipost/templates/scipost/registration_invitation_sent.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends 'scipost/base.html' %}
-
-{% block pagetitle %}: registration invitation sent{% endblock pagetitle %}
-
-{% block content %}
-
-<h1>Registration Invitation sent</h1>
-<p>Return to the <a href="{% url 'scipost:registration_invitations' %}">registration invitations page</a>.</p>
-
-{% endblock content %}
diff --git a/scipost/templates/scipost/registration_invitations.html b/scipost/templates/scipost/registration_invitations.html
deleted file mode 100644
index bcd610d61a0b261225a22c21b222e0e5f2f8f528..0000000000000000000000000000000000000000
--- a/scipost/templates/scipost/registration_invitations.html
+++ /dev/null
@@ -1,121 +0,0 @@
-{% extends 'scipost/_personal_page_base.html' %}
-
-{% block pagetitle %}: registration invitations{% endblock pagetitle %}
-
-{% load scipost_extras %}
-{% load bootstrap %}
-
-{% block breadcrumb_items %}
-    {{block.super}}
-    <span class="breadcrumb-item">Registration invitations</span>
-{% endblock %}
-
-{% block content %}
-
-<script>
-$(document).ready(function(){
-
-    $('#id_invitation_type').on('change', function() {
-        switch ($(this).val()) {
-            case "ci":
-                $("#id_cited_in_submission").parents('.form-group').show();
-                $("#id_cited_in_publication").parents('.form-group').hide();
-            break;
-            case "cp":
-                $("#id_cited_in_submission").parents('.form-group').hide();
-                $("#id_cited_in_publication").parents('.form-group').show();
-            break;
-            default:
-                $("#id_cited_in_submission").parents('.form-group').hide();
-                $("#id_cited_in_publication").parents('.form-group').hide();
-        }
-    }).trigger('change');
-});
-</script>
-
-<div class="row">
-    <div class="col-12">
-        <div class="card card-grey">
-            <div class="card-body">
-                <h1 class="card-title">Registration Invitations</h1>
-                {% if request.user|is_in_group:'SciPost Administrators' %}
-                    <h3>Perform a <a href="{% url 'scipost:registration_invitations_cleanup' %}">cleanup</a> of existing invitations.</h3>
-                {% endif %}
-            </div>
-        </div>
-    </div>
-</div>
-
-<div class="row">
-    <div class="col-12">
-        <h2 class="highlight">Send a new invitation</h2>
-        {% if errormessage %}
-            <h3 class="text-danger">{{ errormessage }}</h3>
-        {% endif %}
-        <form action="{% url 'scipost:registration_invitations' %}" method="post">
-            {% csrf_token %}
-            {{reg_inv_form.media}}
-            {{reg_inv_form|bootstrap}}
-            <input type="submit" class="btn btn-primary" value="Submit">
-        </form>
-    </div>
-</div>
-
-
-<div class="row">
-    <div class="col-12">
-        <h2 class="highlight">Existing drafts (to be processed by Admin)</h2>
-        <a href="javascript:void(0)" class="btn mb-2" data-toggle="toggle" data-target="#table_existing_drafts">view/hide ({{existing_drafts|length}}) +</a>
-
-        <table class="table" id="table_existing_drafts" style="display: none;">
-            <thead>
-                <tr>
-                    <th>Last name</th>
-                    <th>First name</th>
-                    <th>Email</th>
-                    <th>Date drafted</th>
-                    <th>Type</th>
-                    <th>Drafted by</th>
-                    <th colspan="2">Actions</th>
-                </tr>
-            </thead>
-            <tbody>
-                {% for draft in existing_drafts %}
-                  <tr>
-                    <td>{{ draft.last_name }}</td>
-                    <td>{{ draft.first_name }}</td>
-                    <td>{{ draft.email }}</td>
-                    <td>{{ draft.date_drafted }} </td>
-                    <td>{{ draft.get_invitation_type_display }}</td>
-                    <td>{{ draft.drafted_by.user.first_name }} {{ draft.drafted_by.user.last_name }}</td>
-                    <td>
-                        <a href="{% url 'scipost:edit_draft_reg_inv' draft_id=draft.id %}">Edit</a> |
-                        <a href="{% url 'scipost:registration_invitations_from_draft' draft_id=draft.id %}">Process</a> |
-                        <a href="{% url 'scipost:mark_draft_inv_as_processed' draft_id=draft.id %}">Mark as processed</a>
-                    </td>
-                    <td>
-                        <ul>
-                        {% for ac in draft|associated_contributors %}
-                            <li>
-                                <a href="{% url 'scipost:map_draft_reg_inv_to_contributor' draft_id=draft.id contributor_id=ac.id %}">Map to {{ ac.user.first_name }} {{ ac.user.last_name }}</a>
-                            </li>
-                        {% empty %}
-                            <li>No associated contributors found.</li>
-                        {% endfor %}
-                    </ul>
-                    </td>
-                  </tr>
-                {% empty %}
-                    <tr>
-                        <td colspan="8">No drafts found.</td>
-                    </tr>
-                {% endfor %}
-            </tbody>
-        </table>
-    </div>
-</div>
-
-{% include 'scipost/_draft_registration_tables.html' %}
-
-
-{% endblock %}
diff --git a/scipost/templatetags/scipost_extras.py b/scipost/templatetags/scipost_extras.py
index 567bbb642ddddcf99221efdbb6cd3357f89b5dd9..4e9f47e186c03bdb18ae71c828c29efa40108073 100644
--- a/scipost/templatetags/scipost_extras.py
+++ b/scipost/templatetags/scipost_extras.py
@@ -38,8 +38,7 @@ def is_in_group(user, group_name):
 
 @register.filter(name='associated_contributors')
 def associated_contributors(draft):
-    return Contributor.objects.filter(
-        user__last_name__icontains=draft.last_name)
+    return Contributor.objects.filter(user__last_name__icontains=draft.last_name)
 
 
 def is_modulo(counter, total, modulo):
diff --git a/scipost/urls.py b/scipost/urls.py
index 9ab0cf02bef3b221475c65d090119be5b56fd827..9da0a19f216c11051b08f1b2064ccb6774057ea9 100644
--- a/scipost/urls.py
+++ b/scipost/urls.py
@@ -83,44 +83,9 @@ urlpatterns = [
     url(r'^registration_requests$', views.registration_requests, name="registration_requests"),
     url(r'^registration_requests/(?P<contributor_id>[0-9]+)/reset$',
         views.registration_requests_reset, name="registration_requests_reset"),
-    url(r'^registration_invitations/(?P<draft_id>[0-9]+)$',
-        views.registration_invitations, name="registration_invitations_from_draft"),
-    url(r'^registration_invitations$',
-        views.registration_invitations, name="registration_invitations"),
-    url(r'^draft_registration_invitation$',
-        views.draft_registration_invitation, name="draft_registration_invitation"),
-    url(r'^contributors_filter$', views.contributors_filter, name="contributors_filter"),
-    url(r'^edit_draft_reg_inv/(?P<draft_id>[0-9]+)$',
-        views.edit_draft_reg_inv, name="edit_draft_reg_inv"),
-    url(r'^map_draft_reg_inv_to_contributor/(?P<draft_id>[0-9]+)/(?P<contributor_id>[0-9]+)$',
-        views.map_draft_reg_inv_to_contributor, name="map_draft_reg_inv_to_contributor"),
-    url(r'^registration_invitations_cleanup$',
-        views.registration_invitations_cleanup,
-        name="registration_invitations_cleanup"),
-    url(r'^remove_registration_invitation/(?P<invitation_id>[0-9]+)$',
-        views.remove_registration_invitation,
-        name="remove_registration_invitation"),
-    url(r'^edit_invitation_personal_message/(?P<invitation_id>[0-9]+)$',
-        views.edit_invitation_personal_message,
-        name="edit_invitation_personal_message"),
-    url(r'^renew_registration_invitation/(?P<invitation_id>[0-9]+)$',
-        views.renew_registration_invitation,
-        name="renew_registration_invitation"),
-    url(r'^mark_reg_inv_as_declined/(?P<invitation_id>[0-9]+)$',
-        views.mark_reg_inv_as_declined,
-        name="mark_reg_inv_as_declined"),
-    url(r'^registration_invitation_sent$',
-        TemplateView.as_view(template_name='scipost/registration_invitation_sent.html'),
-        name='registration_invitation_sent'),
 
-    # Registration invitations
+    # Registration invitations (Never change this route! Thank you.)
     url(r'^invitation/(?P<key>.+)$', views.invitation, name='invitation'),
-    url(r'^mark_draft_inv_as_processed/(?P<draft_id>[0-9]+)$',
-        views.mark_draft_inv_as_processed, name='mark_draft_inv_as_processed'),
-    url(r'^citation_notifications$',
-        views.citation_notifications, name='citation_notifications'),
-    url(r'^process_citation_notification/(?P<cn_id>[0-9]+)$',
-        views.process_citation_notification, name='process_citation_notification'),
 
     # Authentication
     url(r'^login/$', views.login_view, name='login'),
diff --git a/scipost/utils.py b/scipost/utils.py
index 8c95c5ddc109f882dba1f4052172eaedfce83fe2..de045624ee6fcf1d0e422099b61dcf75f3834aad 100644
--- a/scipost/utils.py
+++ b/scipost/utils.py
@@ -1,17 +1,3 @@
-import datetime
-import hashlib
-import random
-import string
-
-from django.contrib.auth.models import User
-
-from django.core.mail import EmailMultiAlternatives
-from django.core.urlresolvers import reverse
-from django.template import Context, Template
-from django.utils import timezone
-
-from .models import DraftInvitation, RegistrationInvitation
-
 from common.utils import BaseMailUtil
 
 
@@ -83,41 +69,6 @@ class Utils(BaseMailUtil):
     mail_sender = 'registration@scipost.org'
     mail_sender_title = 'SciPost registration'
 
-    @classmethod
-    def password_mismatch(cls):
-        if cls.form.cleaned_data['password'] != cls.form.cleaned_data['password_verif']:
-            return True
-        else:
-            return False
-
-    @classmethod
-    def username_already_taken(cls):
-        if User.objects.filter(username=cls.form.cleaned_data['username']).exists():
-            return True
-        else:
-            return False
-
-    @classmethod
-    def email_already_taken(cls):
-        if User.objects.filter(email=cls.form.cleaned_data['email']).exists():
-            return True
-        else:
-            return False
-
-    @classmethod
-    def email_already_invited(cls):
-        if RegistrationInvitation.objects.filter(email=cls.form.cleaned_data['email']).exists():
-            return True
-        else:
-            return False
-
-    @classmethod
-    def email_already_drafted(cls):
-        if DraftInvitation.objects.filter(email=cls.form.cleaned_data['email']).exists():
-            return True
-        else:
-            return False
-
     @classmethod
     def send_registration_email(cls):
         """
@@ -141,520 +92,3 @@ class Utils(BaseMailUtil):
         cls._send_mail(cls, 'new_activation_link',
                        [cls._context['contributor'].user.email],
                        'new email activation link')
-
-    @classmethod
-    def create_draft_invitation(cls):
-        invitation = DraftInvitation(
-            title=cls.form.cleaned_data['title'],
-            first_name=cls.form.cleaned_data['first_name'],
-            last_name=cls.form.cleaned_data['last_name'],
-            email=cls.form.cleaned_data['email'],
-            invitation_type=cls.form.cleaned_data['invitation_type'],
-            cited_in_submission=cls.form.cleaned_data['cited_in_submission'],
-            cited_in_publication=cls.form.cleaned_data['cited_in_publication'],
-            drafted_by=cls.contributor,
-            )
-        invitation.save()
-
-    @classmethod
-    def create_invitation(cls):
-        invitation = RegistrationInvitation(
-            title=cls.form.cleaned_data['title'],
-            first_name=cls.form.cleaned_data['first_name'],
-            last_name=cls.form.cleaned_data['last_name'],
-            email=cls.form.cleaned_data['email'],
-            invitation_type=cls.form.cleaned_data['invitation_type'],
-            cited_in_submission=cls.form.cleaned_data['cited_in_submission'],
-            cited_in_publication=cls.form.cleaned_data['cited_in_publication'],
-            invited_by=cls.contributor,
-            message_style=cls.form.cleaned_data['message_style'],
-            personal_message=cls.form.cleaned_data['personal_message'],
-            )
-        Utils.load({'invitation': invitation})
-
-    @classmethod
-    def send_registration_invitation_email(cls, renew=False):
-        signature = (cls.invitation.invited_by.get_title_display() + ' '
-                     + cls.invitation.invited_by.user.first_name + ' '
-                     + cls.invitation.invited_by.user.last_name)
-        if not renew:
-            # Generate email activation key and link
-            salt = ""
-            for i in range(5):
-                salt = salt + random.choice(string.ascii_letters)
-            salt = salt.encode('utf8')
-            invitationsalt = cls.invitation.last_name
-            invitationsalt = invitationsalt.encode('utf8')
-            cls.invitation.invitation_key = hashlib.sha1(salt+invitationsalt).hexdigest()
-        cls.invitation.key_expires = datetime.datetime.strftime(
-            datetime.datetime.now() + datetime.timedelta(days=365), "%Y-%m-%d %H:%M:%S")
-        if renew:
-            cls.invitation.nr_reminders += 1
-            cls.invitation.date_last_reminded = timezone.now()
-        cls.invitation.save()
-        email_text = ''
-        email_text_html = ''
-        email_context = {}
-        if renew:
-            email_text += ('Reminder: Invitation to SciPost\n'
-                           '-------------------------------\n\n')
-            email_text_html += ('<strong>Reminder: Invitation to SciPost</strong>'
-                                '<br/><hr/><br/>')
-        if cls.invitation.invitation_type == 'F':
-            email_text += 'RE: Invitation to join the Editorial College of SciPost\n\n'
-            email_text_html += ('<strong>RE: Invitation to join the Editorial College '
-                                'of SciPost</strong><br/><hr/><br/>')
-        email_text += 'Dear '
-        email_text_html += 'Dear '
-        if cls.invitation.message_style == 'F':
-            email_text += cls.invitation.get_title_display() + ' ' + cls.invitation.last_name
-            email_text_html += '{{ title }} {{ last_name }}'
-            email_context['title'] = cls.invitation.get_title_display()
-            email_context['last_name'] = cls.invitation.last_name
-        else:
-            email_text += cls.invitation.first_name
-            email_text_html += '{{ first_name }}'
-            email_context['first_name'] = cls.invitation.first_name
-        email_text += ',\n\n'
-        email_text_html += ',<br/>'
-        if len(cls.invitation.personal_message) > 3:
-            email_text += cls.invitation.personal_message + '\n\n'
-            email_text_html += '\n{{ personal_message|linebreaks }}<br/>\n'
-            email_context['personal_message'] = cls.invitation.personal_message
-
-        # This text to be put in C, ci invitations
-        summary_text = (
-            '\n\nIn summary, SciPost.org is a publication portal managed by '
-            'professional scientists, offering (among others) high-quality '
-            'Open Access journals with innovative forms of refereeing, and a '
-            'means of commenting on all existing literature. SciPost is established as '
-            'a not-for-profit foundation devoted to serving the interests of the '
-            'international scientific community.'
-            '\n\nThe site is anchored at https://scipost.org. Many further details '
-            'about SciPost, its principles, ideals and implementation can be found at '
-            'https://scipost.org/about and https://scipost.org/FAQ.'
-            '\n\nAs a professional academic, you can register at '
-            'https://scipost.org/register, enabling you to contribute to the site\'s '
-            'contents, for example by offering submissions, reports and comments.'
-            '\n\nFor your convenience, a partly pre-filled registration '
-            'form has been prepared for you at '
-            'https://scipost.org/invitation/' + cls.invitation.invitation_key
-            + ' (you can in any case still register at '
-            'https://scipost.org/register).\n\n'
-            'If you do develop sympathy for the initiative, besides participating in the '
-            'online platform, we would be very grateful if you considered submitting a '
-            'publication to one of the journals within the near future, in order to help '
-            'establish their reputation. We\'ll also be looking forward to your reaction, '
-            'comments and suggestions about the initiative, which we hope you will find '
-            'useful to your work as a professional scientist.'
-            '\n\nMany thanks in advance for taking a few minutes to look into it,'
-            '\n\nOn behalf of the SciPost Foundation,\n\n'
-            + signature + '\n'
-        )
-
-        summary_text_html = (
-            '\n<p>In summary, SciPost.org is a publication portal managed by '
-            'professional scientists, offering (among others) high-quality '
-            'Open Access journals with innovative forms of refereeing, and a '
-            'means of commenting on all existing literature. SciPost is established as '
-            'a not-for-profit foundation devoted to serving the interests of the '
-            'international scientific community.</p>'
-            '\n<p>The site is anchored at <a href="https://scipost.org">scipost.org</a>. '
-            'Many further details '
-            'about SciPost, its principles, ideals and implementation can be found at '
-            'the <a href="https://scipost.org/about">about</a> '
-            'and <a href="https://scipost.org/FAQ">FAQ</a> pages.</p>'
-            '<p>As a professional academic, you can register at the '
-            '<a href="https://scipost.org/register">registration page</a>, '
-            'enabling you to contribute to the site\'s '
-            'contents, for example by offering submissions, reports and comments.</p>'
-            '\n<p>For your convenience, a partly pre-filled '
-            '<a href="https://scipost.org/invitation/{{ invitation_key }}">registration form</a>'
-            ' has been prepared for you (you can in any case still register at the '
-            '<a href="https://scipost.org/register">registration page</a>).</p>'
-            '\n<p>If you do develop sympathy for the initiative, besides participating in the '
-            'online platform, we would be very grateful if you considered submitting a '
-            'publication to one of the journals within the near future, in order to help '
-            'establish their reputation. We\'ll also be looking forward to your reaction, '
-            'comments and suggestions about the initiative, which we hope you will find '
-            'useful to your work as a professional scientist.</p>'
-            '\n<p>Many thanks in advance for taking a few minutes to look into it,</p>'
-            '<p>On behalf of the SciPost Foundation,</p>'
-            # '<br/>Prof. dr Jean-Sébastien Caux'
-            # '<br/>---------------------------------------------'
-            # '<br/>Institute for Theoretical Physics'
-            # '<br/>University of Amsterdam'
-            # '<br/>Science Park 904'
-            # '<br/>1098 XH Amsterdam<br/>The Netherlands'
-            # '<br/>---------------------------------------------'
-            # '<br/>tel.: +31 (0)20 5255775'
-            # '<br/>fax: +31 (0)20 5255778'
-            # '<br/>---------------------------------------------'
-            '<p>' + signature + '</p>'
-        )
-        email_context['invitation_key'] = cls.invitation.invitation_key
-
-        if cls.invitation.invitation_type == 'R':
-            # Refereeing invitation
-            # Details of the submission to referee are already in the personal_message field
-            email_text += (
-                'We would hereby like to cordially invite you '
-                'to become a Contributor on SciPost '
-                '(this is required in order to deliver reports; '
-                'our records show that you are not yet registered); '
-                'for your convenience, we have prepared a pre-filled form for you at\n\n'
-                'https://scipost.org/invitation/' + cls.invitation.invitation_key + '\n\n'
-                'after which your registration will be activated, allowing you to contribute, '
-                'in particular by providing referee reports.\n\n'
-                'To ensure timely processing of the submission (out of respect for the authors), '
-                'we would appreciate a quick accept/decline '
-                'response from you, ideally within the next 2 days.\n\n'
-                'If you are not able to provide a Report, you can let us know by simply '
-                'navigating to \n\nhttps://scipost.org/submissions/decline_ref_invitation/'
-                + cls.invitation.invitation_key + '\n\n'
-                'If you are able to provide a Report, you can confirm this after registering '
-                'and logging in (you will automatically be prompted for a confirmation).\n\n'
-                'We very much hope that we can count on your expertise,\n\n'
-                'Many thanks in advance,\n\nThe SciPost Team')
-            email_text_html += (
-                '\n<p>We would hereby like to cordially invite you '
-                'to become a Contributor on SciPost '
-                '(this is required in order to deliver reports; '
-                'our records show that you are not yet registered); '
-                'for your convenience, we have prepared a pre-filled '
-                '<a href="https://scipost.org/invitation/{{ invitation_key }}">registration form</a> '
-                'for you. After activation of your registration, you will be allowed to contribute, '
-                'in particular by providing referee reports.</p>'
-                '<p>To ensure timely processing of the submission (out of respect for the authors), '
-                'we would appreciate a quick accept/decline '
-                'response from you, ideally within the next 2 days.</p>'
-                '<p>If you are <strong>not</strong> able to provide a Report, '
-                'you can let us know by simply '
-                '<a href="https://scipost.org/submissions/decline_ref_invitation/{{ invitation_key }}">'
-                'clicking here</a>.</p>'
-                '<p>If you are able to provide a Report, you can confirm this after registering '
-                'and logging in (you will automatically be prompted for a confirmation).</p>'
-                '<p>We very much hope that we can count on your expertise,</p>'
-                '<p>Many thanks in advance,</p>'
-                '<p>The SciPost Team</p>')
-
-            email_text += SCIPOST_SUMMARY_FOOTER
-            email_text_html += SCIPOST_SUMMARY_FOOTER_HTML
-            email_text_html += '<br/>' + EMAIL_FOOTER
-            html_template = Template(email_text_html)
-            html_version = html_template.render(Context(email_context))
-            emailmessage = EmailMultiAlternatives(
-                'SciPost: refereeing request (and registration invitation)', email_text,
-                'SciPost Refereeing <refereeing@scipost.org>',
-                [cls.invitation.email],
-                cc=[cls.invitation.invited_by.user.email],
-                bcc=['refereeing@scipost.org'],
-                reply_to=['refereeing@scipost.org'])
-            emailmessage.attach_alternative(html_version, 'text/html')
-
-        elif cls.invitation.invitation_type == 'ci':
-            # Has been cited in a Submission. Invite!
-            email_text += (
-                'Your work has been cited in a manuscript submitted to SciPost,'
-                '\n\n' + cls.invitation.cited_in_submission.title
-                + ' by ' + cls.invitation.cited_in_submission.author_list + '.\n\n'
-                'I would hereby like to use this opportunity to quickly introduce '
-                'you to the SciPost initiative, and to invite you to become an active '
-                'Contributor to the site. You might for example consider reporting or '
-                'commenting on the above submission before the refereeing deadline.')
-            email_text_html += (
-                '<p>Your work has been cited in a manuscript submitted to SciPost,</p>'
-                '<p>{{ sub_title }} <br>by {{ sub_author_list }},</p>'
-                '<p>which you can find online at the '
-                '<a href="https://scipost.org/submission/{{ arxiv_nr_w_vn_nr }}">'
-                'submission\'s page</a>.</p>'
-                '\n<p>I would hereby like to use this opportunity to quickly introduce '
-                'you to the SciPost initiative, and to invite you to become an active '
-                'Contributor to the site. You might for example consider reporting or '
-                'commenting on the above submission before the refereeing deadline.</p>')
-            email_context['sub_title'] = cls.invitation.cited_in_submission.title
-            email_context['sub_author_list'] = cls.invitation.cited_in_submission.author_list
-            email_context['arxiv_identifier_w_vn_nr'] = cls.invitation.cited_in_submission.arxiv_identifier_w_vn_nr
-
-            email_text += summary_text
-            email_text_html += summary_text_html
-            email_text_html += '<br/>' + EMAIL_FOOTER
-            html_template = Template(email_text_html)
-            html_version = html_template.render(Context(email_context))
-            emailmessage = EmailMultiAlternatives(
-                'SciPost: invitation', email_text,
-                'SciPost registration <registration@scipost.org>',
-                [cls.invitation.email],
-                cc=[cls.invitation.invited_by.user.email],
-                bcc=['registration@scipost.org'],
-                reply_to=['registration@scipost.org'])
-            emailmessage.attach_alternative(html_version, 'text/html')
-
-        elif cls.invitation.invitation_type == 'cp':
-            # Has been cited in a Publication. Invite!
-            email_text += (
-                'Your work has been cited in a paper published by SciPost,'
-                '\n\n' + cls.invitation.cited_in_publication.title
-                + '\nby ' + cls.invitation.cited_in_publication.author_list +
-                '\n\n(published as ' + cls.invitation.cited_in_publication.citation()
-                + ').\n\n'
-                'I would hereby like to use this opportunity to quickly introduce '
-                'you to the SciPost initiative, and to invite you to become an active '
-                'Contributor to the site.')
-            email_text_html += (
-                '<p>Your work has been cited in a paper published by SciPost,</p>'
-                '<p>{{ pub_title }}</p> <p>by {{ pub_author_list }}</p>'
-                '(published as <a href="https://scipost.org/{{ doi_label }}">{{ citation }}</a>).'
-                '</p>'
-                '\n<p>I would hereby like to use this opportunity to quickly introduce '
-                'you to the SciPost initiative, and to invite you to become an active '
-                'Contributor to the site.</p>')
-            email_context['pub_title'] = cls.invitation.cited_in_publication.title
-            email_context['pub_author_list'] = cls.invitation.cited_in_publication.author_list
-            email_context['doi_label'] = cls.invitation.cited_in_publication.doi_label
-            email_context['citation'] = cls.invitation.cited_in_publication.citation()
-            email_text += summary_text
-            email_text_html += summary_text_html
-            email_text_html += '<br/>' + EMAIL_FOOTER
-            html_template = Template(email_text_html)
-            html_version = html_template.render(Context(email_context))
-            emailmessage = EmailMultiAlternatives(
-                'SciPost: invitation', email_text,
-                'SciPost registration <registration@scipost.org>',
-                [cls.invitation.email],
-                cc=[cls.invitation.invited_by.user.email],
-                bcc=['registration@scipost.org'],
-                reply_to=['registration@scipost.org'])
-            emailmessage.attach_alternative(html_version, 'text/html')
-
-        elif cls.invitation.invitation_type == 'C':
-            email_text += ('I would hereby like to quickly introduce '
-                           'you to a scientific publishing initiative '
-                           'called SciPost, and to invite you to become an active Contributor.')
-            email_text += summary_text
-            email_text_html += (
-                '<p>I would hereby like to quickly introduce '
-                'you to a scientific publishing initiative '
-                'called SciPost, and to invite you to become an active Contributor.</p>')
-            email_text_html += summary_text_html + '<br/>' + EMAIL_FOOTER
-            html_template = Template(email_text_html)
-            html_version = html_template.render(Context(email_context))
-            emailmessage = EmailMultiAlternatives(
-                'SciPost: invitation', email_text,
-                'SciPost registration <registration@scipost.org>',
-                [cls.invitation.email],
-                cc=[cls.invitation.invited_by.user.email],
-                bcc=['registration@scipost.org'],
-                reply_to=['registration@scipost.org'])
-            emailmessage.attach_alternative(html_version, 'text/html')
-
-        elif cls.invitation.invitation_type == 'F':
-            email_text += (
-                '\nYou will perhaps have already heard about SciPost, a publication '
-                'portal established by and for professional scientists.\n'
-                '\nSciPost.org is legally based on a not-for-profit foundation and will '
-                'operate in perpetuity as a non-commercial entity at the exclusive service '
-                'of the academic sector, bringing a cost-slashing alternative to existing '
-                'practices.\n'
-                '\nSciPost offers a collection of two-way open '
-                'access (no subscription fees, no author fees) journals with extremely '
-                'stringent (peer-witnessed) refereeing, overseen by '
-                'our Editorial College (exclusively composed '
-                'of established, professionally practising scientists). The whole process is '
-                'designed to ensure the highest achievable scientific quality while making the '
-                'editorial workflow as light and efficient as possible.\n'
-                '\nTo go straight to the point, on behalf of the foundation '
-                'and in view of your professional expertise, I hereby would '
-                'like to invite you to become an Editorial Fellow and thus join the '
-                'Editorial College of SciPost Physics.\n\n'
-                'Please note that only well-known and respected senior academics are '
-                'being contacted for this purpose. Academic reputation and involvement '
-                'in the community are the most important criteria guiding our '
-                'considerations of who should belong to the Editorial College.\n'
-                '\nTo help you in considering this, it would be best if you were to take '
-                'the time to look at the website itself, which is anchored at scipost.org. '
-                'Besides looking around the side, you can also personally register '
-                '(to become a Contributor, without necessarily committing to membership '
-                'of the Editorial College, this to be discussed separately) by visiting '
-                'the following single-use link, containing a partly pre-filled form for '
-                'your convenience: \n\n'
-                'https://scipost.org/invitation/' + cls.invitation.invitation_key + '.\n'
-                '\nMany details about the initiative '
-                'can then be found at scipost.org/about and at scipost.org/FAQ. '
-                'Functioning of the College will proceed according to the by-laws set '
-                'out in scipost.org/EdCol_by-laws.\n\n'
-                'Since the success of this initiative is dependent on the involvement of '
-                'the very people it is meant to serve, we\'d be very grateful if you were '
-                'to give due consideration to this proposal. We would expect you to '
-                'commit just 2-4 hours per month to help perform Editorial duties; we will '
-                'adjust the number of Editorial Fellows to ensure this is the case. You '
-                'could try it out for 6 months or a year, and of course you could quit '
-                'any time you wished.\n\n'
-                'I\'d be happy to provide you with more information, should you require '
-                'it. In view of our development plans, I would be grateful if you could '
-                'react (by replying to this email) within the next two or three weeks, '
-                'if possible. I\'ll be looking forward to your reaction, your comments '
-                'and suggestions, be they positive or negative. If you need more time '
-                'to consider, that\'s also fine; just let me know.\n\n'
-                'On behalf of the SciPost Foundation,\n\n'
-                'Prof. dr Jean-Sébastien Caux\n---------------------------------------------'
-                '\nInstitute for Theoretial Physics\nUniversity of Amsterdam'
-                '\nScience Park 904\n1098 XH Amsterdam\nThe Netherlands'
-                '\n---------------------------------------------\n'
-                'tel.: +31 (0)20 5255775\nfax: +31 (0)20 5255778'
-                '\n---------------------------------------------')
-            email_text_html += (
-                '\n<p>You will perhaps have already heard about SciPost, a publication '
-                'portal established by and for professional scientists. '
-                '\n<p>SciPost.org is legally based on a not-for-profit foundation and will '
-                'operate in perpetuity as a non-commercial entity at the exclusive service '
-                'of the academic sector, bringing a cost-slashing alternative to existing '
-                'practices.</p>'
-                '<p>SciPost offers a collection of two-way open '
-                'access (no subscription fees, no author fees) journals with extremely '
-                'stringent (peer-witnessed) refereeing, overseen by '
-                'our Editorial College (exclusively composed '
-                'of established, professionally practising scientists). The whole process is '
-                'designed to ensure the highest achievable scientific quality while making the '
-                'editorial workflow as light and efficient as possible.</p>'
-                '\n<p>To go straight to the point, on behalf of the SciPost Foundation '
-                'and in view of your professional expertise, I hereby would '
-                'like to invite you to become an Editorial Fellow and thus join the '
-                'Editorial College of SciPost Physics.</p>'
-                '\n<p>Please note that only well-known and respected senior academics are '
-                'being contacted for this purpose. Academic reputation and involvement '
-                'in the community are the most important criteria guiding our '
-                'considerations of who should belong to the Editorial College.</p>'
-                '\n<p>To help you in considering this, it would be best if you were to take '
-                'the time to look at the website itself, which is anchored at scipost.org. '
-                'Besides looking around the site, you can also personally register '
-                '(to become a Contributor, without necessarily committing to membership '
-                'of the Editorial College, this to be discussed separately) by visiting '
-                'the following <a href="https://scipost.org/invitation/{{ invitation_key }}">'
-                'single-use link</a>, containing a partly pre-filled form for '
-                'your convenience.</p>'
-                '\n<p>Many details about the initiative '
-                'can then be found at scipost.org/about and at scipost.org/FAQ. '
-                'Functioning of the College will proceed according to the by-laws set '
-                'out in scipost.org/EdCol_by-laws.</p>'
-                '\n<p>Since the success of this initiative is dependent on the involvement of '
-                'the very people it is meant to serve, we\'d be very grateful if you were '
-                'to give due consideration to this proposal. We would expect you to '
-                'commit just 2-4 hours per month to help perform Editorial duties; we will '
-                'constantly adjust the number of Editorial Fellows to ensure this is the case. You '
-                'could try it out for 6 months or a year, and of course you could quit '
-                'any time you wished.</p>'
-                '\n<p>I\'d be happy to provide you with more information, should you require '
-                'it. In view of our development plans, I would be grateful if you could '
-                'react (by replying to this email) within the next two or three weeks, '
-                'if possible. I\'ll be looking forward to your reaction, your comments '
-                'and suggestions, be they positive or negative. If you need more time '
-                'to consider, that\'s also fine; just let me know.</p>'
-                '<p>On behalf of the SciPost Foundation,</p>'
-                '<br/>Prof. dr Jean-Sébastien Caux'
-                '<br/>---------------------------------------------'
-                '<br/>Institute for Theoretial Physics'
-                '<br/>University of Amsterdam'
-                '<br/>Science Park 904<br/>1098 XH Amsterdam<br/>The Netherlands'
-                '<br/>---------------------------------------------'
-                '<br/>tel.: +31 (0)20 5255775\nfax: +31 (0)20 5255778'
-                '<br/>---------------------------------------------\n')
-
-            email_text_html += '<br/>' + EMAIL_FOOTER
-            html_template = Template(email_text_html)
-            html_version = html_template.render(Context(email_context))
-            emailmessage = EmailMultiAlternatives(
-                'SciPost registration invitation', email_text,
-                'J-S Caux <jscaux@scipost.org>',
-                [cls.invitation.email],
-                cc=[cls.invitation.invited_by.user.email],
-                bcc=['registration@scipost.org'],
-                reply_to=['registration@scipost.org'])
-            emailmessage.attach_alternative(html_version, 'text/html')
-
-        # This function is now for all invitation types:
-        emailmessage.send(fail_silently=False)
-
-    @classmethod
-    def send_citation_notification_email(cls):
-        """
-        Requires loading the 'notification' attribute.
-        """
-        email_context = {}
-        email_text = ('Dear ' + cls.notification.contributor.get_title_display() +
-                      ' ' + cls.notification.contributor.user.last_name)
-        email_text_html = 'Dear {{ title }} {{ last_name }}'
-        email_context['title'] = cls.notification.contributor.get_title_display()
-        email_context['last_name'] = cls.notification.contributor.user.last_name
-        email_text += ',\n\n'
-        email_text_html += ',<br/>'
-        if cls.notification.cited_in_publication:
-            url_unsubscribe = reverse('scipost:unsubscribe',
-                                      args=[cls.notification.contributor.id,
-                                            cls.notification.contributor.activation_key])
-            email_text += (
-                'We would like to notify you that '
-                'your work has been cited in a paper published by SciPost,'
-                '\n\n' + cls.notification.cited_in_publication.title
-                + '\nby ' + cls.notification.cited_in_publication.author_list +
-                '\n\n(published as ' + cls.notification.cited_in_publication.citation() +
-                ').\n\nWe hope you will find this paper of interest to your own research.'
-                '\n\nBest regards,\n\nThe SciPost Team'
-                '\n\nDon\'t want to receive such emails? Unsubscribe by visiting '
-                + url_unsubscribe + '.')
-            email_text_html += (
-                '<p>We would like to notify you that '
-                'your work has been cited in a paper published by SciPost,</p>'
-                '<p>{{ title }}</p><p>by {{ pub_author_list }}</p>'
-                '<p>(published as <a href="https://scipost.org/{{ doi_label }}">'
-                '{{ citation }}</a>).</p>'
-                '<p>We hope you will find this paper of interest to your own research.</p>'
-                '<p>Best regards,</p><p>The SciPost Team</p><br/>'
-                + EMAIL_FOOTER + '<br/>'
-                '\n<p style="font-size: 10px;">Don\'t want to receive such emails? '
-                '<a href="%s">Unsubscribe</a>.</p>' % url_unsubscribe)
-            email_context['title'] = cls.notification.cited_in_publication.title
-            email_context['pub_author_list'] = cls.notification.cited_in_publication.author_list
-            email_context['doi_label'] = cls.notification.cited_in_publication.doi_label
-            email_context['citation'] = cls.notification.cited_in_publication.citation()
-            email_context['key'] = cls.notification.contributor.activation_key
-            html_template = Template(email_text_html)
-            html_version = html_template.render(Context(email_context))
-        elif cls.notification.cited_in_submission:
-            url_unsubscribe = reverse('scipost:unsubscribe',
-                                      args=[cls.notification.contributor.id,
-                                            cls.notification.contributor.activation_key])
-            email_text += (
-                'Your work has been cited in a manuscript submitted to SciPost,'
-                '\n\n' + cls.notification.cited_in_submission.title
-                + ' by ' + cls.notification.cited_in_submission.author_list + '.\n\n'
-                'You might for example consider reporting or '
-                'commenting on the above submission before the refereeing deadline.\n\n'
-                'Best regards,\n\nThe SciPost Team'
-                '\n\nDon\'t want to receive such emails? Unsubscribe by visiting '
-                + url_unsubscribe + '.')
-            email_text_html += (
-                '<p>Your work has been cited in a manuscript submitted to SciPost,</p>'
-                '<p>{{ sub_title }} <br>by {{ sub_author_list }},</p>'
-                '<p>which you can find online at the '
-                '<a href="https://scipost.org/submission/{{ arxiv_nr_w_vn_nr }}">'
-                'submission\'s page</a>.</p>'
-                '<p>You might for example consider reporting or '
-                'commenting on the above submission before the refereeing deadline.</p>'
-                '<p>Best regards,</p><p>The SciPost Team</p><br/>'
-                + EMAIL_FOOTER + '<br/>'
-                '\n<p style="font-size: 10px;">Don\'t want to receive such emails? '
-                '<a href="%s">Unsubscribe</a>.</p>' % url_unsubscribe)
-            email_context['sub_title'] = cls.notification.cited_in_submission.title
-            email_context['sub_author_list'] = cls.notification.cited_in_submission.author_list
-            email_context['arxiv_identifier_w_vn_nr'] = cls.notification.cited_in_submission.arxiv_identifier_w_vn_nr
-            email_context['key'] = cls.notification.contributor.activation_key
-
-        emailmessage = EmailMultiAlternatives(
-            'SciPost: citation notification', email_text,
-            'SciPost admin <admin@scipost.org>',
-            [cls.notification.contributor.user.email],
-            bcc=['admin@scipost.org'],
-            reply_to=['admin@scipost.org'])
-        emailmessage.attach_alternative(html_version, 'text/html')
-        emailmessage.send(fail_silently=False)
diff --git a/scipost/views.py b/scipost/views.py
index b9d8656f910498ac50b5d7993d097c7f8b7eb00b..3097897d4be736bc225a97543b6cc026cebd921c 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -23,29 +23,27 @@ from django.views.debug import cleanse_setting
 from django.views.static import serve
 
 from guardian.decorators import permission_required
-from guardian.shortcuts import assign_perm, get_objects_for_user
 from haystack.generic_views import SearchView
 
 from .constants import SCIPOST_SUBJECT_AREAS, subject_areas_raw_dict, SciPost_from_addresses_dict,\
                        CONTRIBUTOR_NORMAL
 from .decorators import has_contributor
-from .models import Contributor, CitationNotification, UnavailabilityPeriod,\
-                    DraftInvitation, RegistrationInvitation,\
+from .models import Contributor, UnavailabilityPeriod,\
                     AuthorshipClaim, EditorialCollege, EditorialCollegeFellowship
-from .forms import AuthenticationForm, DraftInvitationForm, UnavailabilityPeriodForm,\
-                   RegistrationForm, RegistrationInvitationForm, AuthorshipClaimForm,\
-                   ModifyPersonalMessageForm, SearchForm, VetRegistrationForm, reg_ref_dict,\
+from .forms import AuthenticationForm, UnavailabilityPeriodForm,\
+                   RegistrationForm, AuthorshipClaimForm,\
+                   SearchForm, VetRegistrationForm, reg_ref_dict,\
                    UpdatePersonalDataForm, UpdateUserDataForm, PasswordChangeForm,\
-                   EmailGroupMembersForm, EmailParticularForm, SendPrecookedEmailForm,\
-                   ContributorsFilterForm
+                   EmailGroupMembersForm, EmailParticularForm, SendPrecookedEmailForm
 from .utils import Utils, EMAIL_FOOTER, SCIPOST_SUMMARY_FOOTER, SCIPOST_SUMMARY_FOOTER_HTML
 
 from affiliations.forms import AffiliationsFormset
 from colleges.permissions import fellowship_or_admin_required
 from commentaries.models import Commentary
 from comments.models import Comment
+from invitations.constants import STATUS_REGISTERED
+from invitations.models import RegistrationInvitation
 from journals.models import Publication, Journal, PublicationAuthorsTable
-from mails.views import MailEditingSubView
 from news.models import NewsItem
 from submissions.models import Submission, RefereeInvitation,\
                                Report, EICRecommendation
@@ -140,8 +138,8 @@ def register(request):
         Utils.send_registration_email()
 
         # Disable invitations related to the new Contributor
-        (RegistrationInvitation.objects.filter(email=form.cleaned_data['email'])
-         .update(responded=True))
+        RegistrationInvitation.objects.declined_or_without_response().filter(
+            email=form.cleaned_data['email']).update(status=STATUS_REGISTERED)
 
         context = {
             'ack_header': 'Thanks for registering to SciPost.',
@@ -163,7 +161,7 @@ def invitation(request, key):
     the default registration form.
     """
     invitation = get_object_or_404(RegistrationInvitation, invitation_key=key)
-    if invitation.responded:
+    if invitation.has_responded:
         errormessage = ('This invitation token has already been used, '
                         'or this email address is already associated to a registration.')
     elif timezone.now() > invitation.key_expires:
@@ -352,286 +350,6 @@ def registration_requests_reset(request, contributor_id):
     return redirect(reverse('scipost:registration_requests'))
 
 
-@permission_required('scipost.can_draft_registration_invitations', return_403=True)
-def draft_registration_invitation(request):
-    """
-    For officers to prefill registration invitations.
-    This is similar to the registration_invitations method,
-    which is used to complete the invitation process.
-    """
-    form = DraftInvitationForm(request.POST or None, current_user=request.user)
-    if form.is_valid():
-        invitation = form.save(commit=False)
-        invitation.drafted_by = request.user.contributor
-        invitation.save()
-
-        # Assign permission to 'drafter' to edit the draft afterwards
-        assign_perm('comments.change_draftinvitation', request.user, invitation)
-        messages.success(request, 'Draft invitation saved.')
-        return redirect(reverse('scipost:draft_registration_invitation'))
-
-    existing_drafts = DraftInvitation.objects.filter(processed=False).order_by('last_name')
-
-    context = {
-        'form': form,
-        'existing_drafts': existing_drafts,
-    }
-    return render(request, 'scipost/draft_registration_invitation.html', context)
-
-
-@permission_required('scipost.can_draft_registration_invitations', return_403=True)
-def contributors_filter(request):
-    """
-    For Invitation Officers that use lists of scientists as a to-do. This
-    view returns all entries of those lists with users that are certainly not registered
-    or invitated.
-    """
-    names_found = names_not_found = invitations_found = None
-    form = ContributorsFilterForm(request.POST or None)
-    if form.is_valid():
-        names_found, names_not_found, invitations_found = form.filter()
-
-    context = {
-        'form': form,
-        'names_found': names_found,
-        'names_not_found': names_not_found,
-        'invitations_found': invitations_found,
-    }
-    return render(request, 'scipost/contributors_filter.html', context)
-
-
-@login_required
-def edit_draft_reg_inv(request, draft_id):
-    """
-    Edit DraftInvitation instance. It's only possible to edit istances created by the User itself.
-    """
-    draft = get_object_or_404((get_objects_for_user(request.user, 'scipost.change_draftinvitation')
-                               .filter(processed=False)),
-                              id=draft_id)
-
-    draft_inv_form = DraftInvitationForm(request.POST or None, current_user=request.user,
-                                         instance=draft)
-    if draft_inv_form.is_valid():
-        draft = draft_inv_form.save()
-        messages.success(request, 'Draft invitation saved.')
-        return redirect(reverse('scipost:registration_invitations'))
-
-    context = {'draft_inv_form': draft_inv_form}
-    return render(request, 'scipost/edit_draft_reg_inv.html', context)
-
-
-@permission_required('scipost.can_manage_registration_invitations', return_403=True)
-def map_draft_reg_inv_to_contributor(request, draft_id, contributor_id):
-    """
-    If a draft invitation actually points to an already-registered
-    Contributor, this method marks the draft invitation as processed
-    and, if the draft invitation was for a citation type,
-    creates an instance of CitationNotification.
-    """
-    draft = get_object_or_404(DraftInvitation, id=draft_id)
-    contributor = get_object_or_404(Contributor, id=contributor_id)
-    draft.processed = True
-    draft.save()
-    citation = CitationNotification(
-        contributor=contributor,
-        cited_in_submission=draft.cited_in_submission,
-        cited_in_publication=draft.cited_in_publication,
-        processed=False)
-    citation.save()
-    return redirect(reverse('scipost:registration_invitations'))
-
-
-@permission_required('scipost.can_invite_Fellows', return_403=True)
-def registration_invitations(request, draft_id=None):
-    """ Overview and tools for administrators """
-    # List invitations sent; send new ones
-    associated_contributors = None
-    initial = {}
-    if draft_id:
-        # Fill draft data if draft_id given
-        draft = get_object_or_404(DraftInvitation, id=draft_id)
-        associated_contributors = Contributor.objects.filter(
-            user__last_name__icontains=draft.last_name)
-        initial = {
-            'title': draft.title,
-            'first_name': draft.first_name,
-            'last_name': draft.last_name,
-            'email': draft.email,
-            'invitation_type': draft.invitation_type,
-            'cited_in_submission': draft.cited_in_submission,
-            'cited_in_publication': draft.cited_in_publication,
-        }
-
-    # Send invitation from form information
-    reg_inv_form = RegistrationInvitationForm(request.POST or None, initial=initial,
-                                              current_user=request.user)
-    if reg_inv_form.is_valid():
-        invitation = reg_inv_form.save(commit=False)
-        invitation.invited_by = request.user.contributor
-        invitation.save()
-        invitation.refresh_keys()
-
-        Utils.load({'invitation': invitation})
-        Utils.send_registration_invitation_email()
-        (DraftInvitation.objects.filter(email=reg_inv_form.cleaned_data['email'])
-         .update(processed=True))
-
-        messages.success(request, 'Registration Invitation sent')
-        return redirect(reverse('scipost:registration_invitations'))
-
-    sent_reg_inv = RegistrationInvitation.objects.filter(responded=False, declined=False)
-    sent_reg_inv_fellows = sent_reg_inv.filter(invitation_type='F').order_by('last_name')
-    sent_reg_inv_contrib = sent_reg_inv.filter(invitation_type='C').order_by('last_name')
-    sent_reg_inv_ref = sent_reg_inv.filter(invitation_type='R').order_by('last_name')
-    sent_reg_inv_cited_sub = sent_reg_inv.filter(invitation_type='ci').order_by('last_name')
-    sent_reg_inv_cited_pub = sent_reg_inv.filter(invitation_type='cp').order_by('last_name')
-
-    resp_reg_inv = RegistrationInvitation.objects.filter(responded=True, declined=False)
-    resp_reg_inv_fellows = resp_reg_inv.filter(invitation_type='F').order_by('last_name')
-    resp_reg_inv_contrib = resp_reg_inv.filter(invitation_type='C').order_by('last_name')
-    resp_reg_inv_ref = resp_reg_inv.filter(invitation_type='R').order_by('last_name')
-    resp_reg_inv_cited_sub = resp_reg_inv.filter(invitation_type='ci').order_by('last_name')
-    resp_reg_inv_cited_pub = resp_reg_inv.filter(invitation_type='cp').order_by('last_name')
-
-    decl_reg_inv = RegistrationInvitation.objects.filter(responded=True, declined=True)
-
-    names_reg_contributors = Contributor.objects.filter(
-        status=1).order_by('user__last_name').values_list(
-        'user__first_name', 'user__last_name')
-    existing_drafts = DraftInvitation.objects.filter(processed=False).order_by('last_name')
-
-    context = {
-        'reg_inv_form': reg_inv_form,
-        'sent_reg_inv_fellows': sent_reg_inv_fellows,
-        'sent_reg_inv_contrib': sent_reg_inv_contrib,
-        'sent_reg_inv_ref': sent_reg_inv_ref,
-        'sent_reg_inv_cited_sub': sent_reg_inv_cited_sub,
-        'sent_reg_inv_cited_pub': sent_reg_inv_cited_pub,
-        'resp_reg_inv_fellows': resp_reg_inv_fellows,
-        'resp_reg_inv_contrib': resp_reg_inv_contrib,
-        'resp_reg_inv_ref': resp_reg_inv_ref,
-        'resp_reg_inv_cited_sub': resp_reg_inv_cited_sub,
-        'resp_reg_inv_cited_pub': resp_reg_inv_cited_pub,
-        'decl_reg_inv': decl_reg_inv,
-        'names_reg_contributors': names_reg_contributors,
-        'existing_drafts': existing_drafts,
-        'associated_contributors': associated_contributors,
-    }
-    return render(request, 'scipost/registration_invitations.html', context)
-
-
-@permission_required('scipost.can_manage_registration_invitations', return_403=True)
-def registration_invitations_cleanup(request):
-    """
-    Compares the email addresses of invitations with those in the
-    database of registered Contributors. Flags overlaps.
-    """
-    contributor_email_list = Contributor.objects.values_list('user__email', flat=True)
-    invs_to_cleanup = RegistrationInvitation.objects.filter(
-        responded=False, email__in=contributor_email_list)
-    context = {'invs_to_cleanup': invs_to_cleanup}
-    return render(request, 'scipost/registration_invitations_cleanup.html', context)
-
-
-@permission_required('scipost.can_manage_registration_invitations', return_403=True)
-def remove_registration_invitation(request, invitation_id):
-    """
-    Remove an invitation (called from registration_invitations_cleanup).
-    """
-    invitation = get_object_or_404(RegistrationInvitation, pk=invitation_id)
-    invitation.delete()
-    return redirect(reverse('scipost:registration_invitations_cleanup'))
-
-
-@permission_required('scipost.can_manage_registration_invitations', return_403=True)
-def edit_invitation_personal_message(request, invitation_id):
-    """
-
-    DOES THIS THING STILL WORK? OR CAN IT BE REMOVED?
-
-    -- JdW (August 14th, 2017)
-
-    """
-    invitation = get_object_or_404(RegistrationInvitation, pk=invitation_id)
-    errormessage = None
-    if request.method == 'POST':
-        form = ModifyPersonalMessageForm(request.POST)
-        if form.is_valid():
-            invitation.personal_message = form.cleaned_data['personal_message']
-            invitation.save()
-            return redirect(reverse('scipost:registration_invitations'))
-        else:
-            errormessage = 'The form was invalid.'
-    else:
-        form = ModifyPersonalMessageForm(
-            initial={'personal_message': invitation.personal_message, })
-    context = {'invitation': invitation,
-               'form': form, 'errormessage': errormessage, }
-    return render(request, 'scipost/edit_invitation_personal_message.html', context)
-
-
-@permission_required('scipost.can_invite_Fellows', return_403=True)
-def renew_registration_invitation(request, invitation_id):
-    """
-    Renew an invitation (called from registration_invitations).
-    """
-    invitation = get_object_or_404(RegistrationInvitation, pk=invitation_id)
-
-    # Utils.load({'invitation': invitation})
-    # Utils.send_registration_invitation_email(True)
-    mail_request = MailEditingSubView(request, mail_code='registration_invitation_renewal',
-                                      invitation=invitation)
-    if mail_request.is_valid():
-        invitation.nr_reminders += 1
-        invitation.date_last_reminded = timezone.now()
-        invitation.save()
-        invitation.refresh_keys()
-        messages.success(request, 'Registration invitation has been sent.')
-        mail_request.send()
-        return redirect('scipost:registration_invitations')
-    else:
-        return mail_request.return_render()
-
-
-@permission_required('scipost.can_manage_registration_invitations', return_403=True)
-def mark_reg_inv_as_declined(request, invitation_id):
-    """
-    Mark an invitation as declined (called from registration_invitations.html).
-    """
-    invitation = get_object_or_404(RegistrationInvitation, pk=invitation_id)
-    invitation.responded = True
-    invitation.declined = True
-    invitation.save()
-    return redirect(reverse('scipost:registration_invitations'))
-
-
-@permission_required('scipost.can_manage_registration_invitations', return_403=True)
-def citation_notifications(request):
-    unprocessed_notifications = CitationNotification.objects.filter(
-        processed=False).order_by('contributor__user__last_name')
-    context = {'unprocessed_notifications': unprocessed_notifications, }
-    return render(request, 'scipost/citation_notifications.html', context)
-
-
-@permission_required('scipost.can_manage_registration_invitations', return_403=True)
-def process_citation_notification(request, cn_id):
-    notification = get_object_or_404(CitationNotification, id=cn_id)
-    notification.processed = True
-    notification.save()
-    if notification.contributor.accepts_SciPost_emails:
-        Utils.load({'notification': notification})
-        Utils.send_citation_notification_email()
-    return redirect(reverse('scipost:citation_notifications'))
-
-
-@permission_required('scipost.can_manage_registration_invitations', return_403=True)
-def mark_draft_inv_as_processed(request, draft_id):
-    draft = get_object_or_404(DraftInvitation, id=draft_id)
-    draft.processed = True
-    draft.save()
-    return redirect(reverse('scipost:registration_invitations'))
-
-
 def login_view(request):
     """
     This view shows and processes a user's login session.
diff --git a/submissions/models.py b/submissions/models.py
index 99b5b94ce18dec2828e43db2d9f45e4ae93668c9..61d1f8555517f1afd33a60a1a6dd7b0a5c1f589b 100644
--- a/submissions/models.py
+++ b/submissions/models.py
@@ -336,11 +336,11 @@ class RefereeInvitation(SubmissionRelatedObjectMixin, models.Model):
     referee = models.ForeignKey('scipost.Contributor', related_name='referee_invitations',
                                 blank=True, null=True, on_delete=models.CASCADE)
     title = models.CharField(max_length=4, choices=TITLE_CHOICES)
-    first_name = models.CharField(max_length=30, default='')
-    last_name = models.CharField(max_length=30, default='')
+    first_name = models.CharField(max_length=30)
+    last_name = models.CharField(max_length=30)
     email_address = models.EmailField()
     # if Contributor not found, person is invited to register
-    invitation_key = models.CharField(max_length=40, default='')
+    invitation_key = models.CharField(max_length=40)
     date_invited = models.DateTimeField(default=timezone.now)
     invited_by = models.ForeignKey('scipost.Contributor', related_name='referee_invited_by',
                                    blank=True, null=True, on_delete=models.CASCADE)
diff --git a/submissions/templatetags/lookup.py b/submissions/templatetags/lookup.py
index c2f7e549124208067d554ffd7ce304d5f8758ae2..5f1f0e24ff0f9a393da923fd739a879d7524af18 100644
--- a/submissions/templatetags/lookup.py
+++ b/submissions/templatetags/lookup.py
@@ -27,4 +27,4 @@ class SubmissionLookup(LookupChannel):
         Right now only used for draft registration invitations. May be extended in the
         future for other purposes as well.
         """
-        return request.user.has_perm('can_draft_registration_invitations')
+        return request.user.has_perm('can_create_registration_invitations')
diff --git a/submissions/views.py b/submissions/views.py
index 813cb8e31aff54214618e413461e2e0ebc71ae5b..3e38c390df9dbd678cb6a5da184c86304a5ded8f 100644
--- a/submissions/views.py
+++ b/submissions/views.py
@@ -39,10 +39,12 @@ from colleges.permissions import fellowship_required, fellowship_or_admin_requir
 from mails.views import MailEditingSubView
 from scipost.forms import ModifyPersonalMessageForm, RemarkForm
 from scipost.mixins import PaginationMixin
-from scipost.models import Contributor, Remark, RegistrationInvitation
-from scipost.utils import Utils
+from scipost.models import Contributor, Remark
 
 from comments.forms import CommentForm
+from invitations.constants import INVITATION_REFEREEING
+from invitations.models import RegistrationInvitation
+from mails.utils import DirectMailUtil
 from production.forms import ProofsDecisionForm
 from production.models import ProductionStream
 
@@ -765,44 +767,44 @@ def recruit_referee(request, arxiv_identifier_w_vn_nr):
         ref_recruit_form = RefereeRecruitmentForm(request.POST)
         if ref_recruit_form.is_valid():
             # TODO check if email already taken
-            ref_invitation = RefereeInvitation(
-                submission=submission,
-                title=ref_recruit_form.cleaned_data['title'],
-                first_name=ref_recruit_form.cleaned_data['first_name'],
-                last_name=ref_recruit_form.cleaned_data['last_name'],
-                email_address=ref_recruit_form.cleaned_data['email_address'],
-                date_invited=timezone.now(),
-                invited_by=request.user.contributor)
-            ref_invitation.save()
+            ref_invitation = ref_recruit_form.save(commit=False)
+            ref_invitation.submission = submission
+            ref_invitation.invited_by = request.user.contributor
+
             # Create and send a registration invitation
-            ref_inv_message_head = ('On behalf of the Editor-in-charge ' +
-                                    submission.editor_in_charge.get_title_display() + ' ' +
-                                    submission.editor_in_charge.user.last_name +
-                                    ', we would like to invite you to referee a Submission to ' +
-                                    submission.get_submitted_to_journal_display() +
-                                    ', namely\n\n' + submission.title +
-                                    '\nby ' + submission.author_list +
-                                    '\n (see https://scipost.org/submission/'
-                                    + submission.arxiv_identifier_w_vn_nr + ').')
+            ref_inv_message_head = (
+                'On behalf of the Editor-in-charge {eic_title} {eic_last_name}, we would'
+                'like to invite you to referee a Submission to {journal}, namely'
+                '\n{sub_title}'
+                '\nby {sub_author_list}'
+                '\n(see https://scipost.org/{sub_url}).'
+                ).format(
+                    eic_title=submission.editor_in_charge.get_title_display(),
+                    eic_last_name=submission.editor_in_charge.user.last_name,
+                    journal=submission.get_submitted_to_journal_display(),
+                    sub_title=submission.title,
+                    sub_author_list=submission.author_list,
+                    sub_url=submission.get_absolute_url())
             reg_invitation = RegistrationInvitation(
                 title=ref_recruit_form.cleaned_data['title'],
                 first_name=ref_recruit_form.cleaned_data['first_name'],
                 last_name=ref_recruit_form.cleaned_data['last_name'],
                 email=ref_recruit_form.cleaned_data['email_address'],
-                invitation_type='R',
-                invited_by=request.user.contributor,
-                message_style='F',
-                personal_message=ref_inv_message_head,
-            )
+                invitation_type=INVITATION_REFEREEING,
+                created_by=request.user.contributor.user,
+                invited_by=request.user.contributor.user,
+                personal_message=ref_inv_message_head)
+
             reg_invitation.save()
-            Utils.load({'invitation': reg_invitation})
-            Utils.send_registration_invitation_email()
+            # Copy the key to the refereeing invitation
+            ref_invitation.invitation_key = reg_invitation.invitation_key
+            ref_invitation.save()
+            mail_sender = DirectMailUtil(mail_code='registration_invitation',
+                                         instance=reg_invitation)
+            mail_sender.send()
             submission.add_event_for_author('A referee has been invited.')
             submission.add_event_for_eic('%s has been recruited and invited as a referee.'
                                          % ref_recruit_form.cleaned_data['last_name'])
-            # Copy the key to the refereeing invitation:
-            ref_invitation.invitation_key = reg_invitation.invitation_key
-            ref_invitation.save()
 
     return redirect(reverse('submissions:editorial_page',
                             kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr}))