From a9ce7384d986b562b78940d761b5f6d54893a2b1 Mon Sep 17 00:00:00 2001
From: Jorran de Wit <jorrandewit@outlook.com>
Date: Mon, 19 Feb 2018 10:48:24 +0100
Subject: [PATCH] Mails ready; testing may start

---
 invitations/forms.py                          |  11 --
 invitations/mixins.py                         |  29 +--
 invitations/models.py                         |  26 ++-
 .../registrationinvitation_form_add_new.html  |  10 +-
 .../citationnotification_table.html           |  78 ++++----
 .../registrationinvitation_table.html         |   2 +-
 invitations/views.py                          |  52 +++--
 mails/forms.py                                |   6 +-
 mails/mixins.py                               |  41 ++--
 .../mail_templates/citation_notification.html |  65 +++++++
 .../mail_templates/citation_notification.json |   8 +
 .../registration_invitation.html              | 181 +++++-------------
 .../registration_invitation_reminder.html     |   4 +
 .../registration_invitation_reminder.json     |   8 +
 .../registration_invitation_renewal.html      |   2 +-
 15 files changed, 294 insertions(+), 229 deletions(-)
 create mode 100644 mails/templates/mail_templates/citation_notification.html
 create mode 100644 mails/templates/mail_templates/citation_notification.json
 create mode 100644 mails/templates/mail_templates/registration_invitation_reminder.html
 create mode 100644 mails/templates/mail_templates/registration_invitation_reminder.json

diff --git a/invitations/forms.py b/invitations/forms.py
index a6afd6c4d..afd2f6313 100644
--- a/invitations/forms.py
+++ b/invitations/forms.py
@@ -73,17 +73,6 @@ class CitationNotificationProcessForm(AcceptRequestMixin, forms.ModelForm):
     def get_all_notifications(self):
         return self.instance.related_notifications().unprocessed()
 
-    def save(self, *args, **kwargs):
-        if kwargs.get('commit', True):
-            self.get_all_notifications().update(processed=True)
-
-            contributor = self.get_all_notifications().filter(contributor__isnull=False)[0]
-            send_mail = (contributor and contributor.accepts_SciPost_emails) or not contributor
-            if send_mail:
-                Utils.load({'notifications': self.get_all_notifications()})
-                Utils.citation_notifications_email()
-        return super().save(*args, **kwargs)
-
 
 class RegistrationInvitationAddCitationForm(AcceptRequestMixin, forms.ModelForm):
     cited_in_submissions = AutoCompleteSelectMultipleField('submissions_lookup', required=False)
diff --git a/invitations/mixins.py b/invitations/mixins.py
index 4b23a2f30..8635746bd 100644
--- a/invitations/mixins.py
+++ b/invitations/mixins.py
@@ -2,8 +2,6 @@ from django.db import transaction
 from django.contrib import messages
 from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
 
-from .utils import Utils
-
 
 class RequestArgumentMixin:
     """
@@ -23,19 +21,28 @@ 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):
-        response = super().form_valid(form)
-        send_mail = self.request.POST.get('save', '') == 'save_and_send'
-        if send_mail:
-            # Confirm permissions for user
-            send_mail = self.request.user.has_perm('scipost.can_manage_registration_invitations')
+        # Communication with the user.
         model_name = self.object._meta.verbose_name
-        if send_mail:
+        model_name = model_name[:1].upper() + model_name[1:]  # Hack it to capitalize the name
+        if self.send_mail:
             self.object.mail_sent()
-            Utils.load({model_name: self.object})
-            getattr(Utils, self.utils_email_method)()
             messages.success(self.request, '{} updated and sent'.format(model_name))
         else:
             messages.success(self.request, '{} updated'.format(model_name))
-        return response
+        return super().form_valid(form)
diff --git a/invitations/models.py b/invitations/models.py
index cf8bc8cfd..66d245832 100644
--- a/invitations/models.py
+++ b/invitations/models.py
@@ -160,5 +160,29 @@ class CitationNotification(models.Model):
         self.save()
 
     def related_notifications(self):
-        return CitationNotification.objects.filter(
+        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/registrationinvitation_form_add_new.html b/invitations/templates/invitations/registrationinvitation_form_add_new.html
index b8e3129f0..3f3fae145 100644
--- a/invitations/templates/invitations/registrationinvitation_form_add_new.html
+++ b/invitations/templates/invitations/registrationinvitation_form_add_new.html
@@ -16,6 +16,9 @@
 <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 contributor_search_form %}
             <h3 class="mb-1">Search for existing Contributor</h3>
@@ -31,6 +34,9 @@
             {% if contributor_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>
@@ -39,7 +45,9 @@
                 {% endif %}
 
                 <h3>Citation Notification</h3>
-                <br>
+                <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={{ contributor_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 %}
diff --git a/invitations/templates/partials/invitations/citationnotification_table.html b/invitations/templates/partials/invitations/citationnotification_table.html
index d2e3a598e..7ec0ed3b9 100644
--- a/invitations/templates/partials/invitations/citationnotification_table.html
+++ b/invitations/templates/partials/invitations/citationnotification_table.html
@@ -12,38 +12,52 @@
     </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>
-                <a href="{% url 'invitations:citation_notification_process' notification.id %}">Process citation</a>
-            </td>
-        </tr>
+              <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_table.html b/invitations/templates/partials/invitations/registrationinvitation_table.html
index ad2056ab8..8bd8eac5c 100644
--- a/invitations/templates/partials/invitations/registrationinvitation_table.html
+++ b/invitations/templates/partials/invitations/registrationinvitation_table.html
@@ -18,7 +18,7 @@
           <tr>
             <td>{{ invitation.last_name }}, {{ invitation.first_name }}</td>
             <td>{{ invitation.email }}</td>
-            <td>{{ invitation.get_status_display }}</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>
diff --git a/invitations/views.py b/invitations/views.py
index 024172cfc..406ef0459 100644
--- a/invitations/views.py
+++ b/invitations/views.py
@@ -28,7 +28,7 @@ class RegistrationInvitationsView(PermissionsMixin, ListView):
         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('status', 'last_name')
+        context['object_list'] = context['object_list'].order_by('date_sent_last', 'last_name')
         context['search_form'] = search_form
         return context
 
@@ -45,11 +45,24 @@ class CitationNotificationsView(PermissionsMixin, ListView):
         'invitation', 'contributor', 'contributor__user')
 
 
-class CitationNotificationsProcessView(PermissionsMixin, RequestArgumentMixin, UpdateView):
+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
@@ -62,23 +75,25 @@ def create_registration_invitation_or_citation(request):
     """
     contributors = []
     suggested_invitations = []
-    contributor_search_form = ContributorSearchForm(request.GET or None)
+
+    # 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']
+    contributor_search_form = ContributorSearchForm(search_data or None,
+                                                    initial=initial_search_data)
     if contributor_search_form.is_valid():
         contributors, suggested_invitations = contributor_search_form.search()
     citation_form = CitationNotificationForm(request.POST or None, contributors=contributors,
                                              prefix='notification', request=request)
 
-    # 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')
-
     # New citation is related to a Contributor: RegistationInvitation
     invitation_form = RegistrationInvitationForm(request.POST or None, request=request,
-                                                 prefix='invitation')
+                                                 prefix='invitation',
+                                                 initial=initial_search_data)
     if invitation_form.is_valid():
         invitation_form.save()
         messages.success(request, 'New Registration Invitation created')
@@ -86,6 +101,14 @@ def create_registration_invitation_or_citation(request):
             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 = {
         'contributor_search_form': contributor_search_form,
         'citation_form': citation_form,
@@ -99,7 +122,6 @@ class RegistrationInvitationsUpdateView(RequestArgumentMixin, PermissionsMixin,
                                         SaveAndSendFormMixin, MailEditorMixin, UpdateView):
     permission_required = 'scipost.can_create_registration_invitations'
     form_class = RegistrationInvitationForm
-    utils_email_method = 'invite_contributor_email'
     mail_code = 'registration_invitation'
 
     def get_context_data(self, **kwargs):
@@ -147,13 +169,13 @@ class RegistrationInvitationsMapToContributorView(RequestArgumentMixin, Permissi
 
 
 class RegistrationInvitationsReminderView(RequestArgumentMixin, PermissionsMixin,
-                                          SaveAndSendFormMixin, UpdateView):
+                                          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'
-    utils_email_method = 'invite_contributor_reminder_email'
+    mail_code = 'registration_invitation_reminder'
 
 
 class RegistrationInvitationsDeleteView(PermissionsMixin, DeleteView):
diff --git a/mails/forms.py b/mails/forms.py
index 4eee0da3e..e8058210d 100644
--- a/mails/forms.py
+++ b/mails/forms.py
@@ -40,7 +40,11 @@ class EmailTemplateForm(forms.Form):
         # Gather meta data
         json_location = '%s/mails/templates/mail_templates/%s.json' % (settings.BASE_DIR,
                                                                        self.mail_code)
-        self.mail_data = json.loads(open(json_location).read())
+        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))
 
         # Digest the templates
         mail_template = loader.get_template('mail_templates/%s.html' % self.mail_code)
diff --git a/mails/mixins.py b/mails/mixins.py
index 4d07529c8..5c08fdbc0 100644
--- a/mails/mixins.py
+++ b/mails/mixins.py
@@ -1,3 +1,5 @@
+from django.contrib import messages
+
 from .forms import EmailTemplateForm, HiddenDataForm
 
 
@@ -10,6 +12,7 @@ class MailEditorMixin:
     """
     object = None
     mail_form = None
+    has_permission_to_send_mail = True
 
     def __init__(self, *args, **kwargs):
         if not self.mail_code:
@@ -28,6 +31,9 @@ class MailEditorMixin:
         """
         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():
@@ -47,29 +53,14 @@ class MailEditorMixin:
         """
         If both the regular form and mailing form are valid, save the form and run the mail form.
         """
-        self.mail_form.send()
-        return super().form_valid(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)
 
-
-    # def __init__(self, request, mail_code, **kwargs):
-    #     self.request = request
-    #     self.context = kwargs.get('context', {})
-    #     self.template_name = kwargs.get('template', 'mails/mail_form.html')
-    #     self.mail_form = EmailTemplateForm(request.POST or None, mail_code=mail_code, **kwargs)
-    #
-    # @property
-    # 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()
-    #
-    # def send(self):
-    #     return self.mail_form.send()
-    #
-    # def return_render(self):
-    #     self.context['form'] = self.mail_form
-    #     return render(self.request, self.template_name, self.context)
+        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)
diff --git a/mails/templates/mail_templates/citation_notification.html b/mails/templates/mail_templates/citation_notification.html
new file mode 100644
index 000000000..61021214c
--- /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 000000000..a93148193
--- /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
index 4990434fc..b0b1349ae 100644
--- a/mails/templates/mail_templates/registration_invitation.html
+++ b/mails/templates/mail_templates/registration_invitation.html
@@ -14,30 +14,21 @@ Dear {% if invitation.message_style == 'F' %}{{ invitation.get_title_display }}
 
 
 {% 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_key }}">registration form</a>
-        for you. After activation of your registration, you will be allowed to contribute,
-        in particular by providing referee reports.
+        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.
+        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>.
+        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).
+        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,
@@ -46,121 +37,50 @@ Dear {% if invitation.message_style == 'F' %}{{ invitation.get_title_display }}
         <br>
         The SciPost Team
     </p>
-{% elif invitation.invitation_type == 'ci' %}
-    <p>
-        Your work has been cited in a manuscript submitted to SciPost,
-    </p>
-    <p>{{ invitation.cited_in_submission.title }} <br>by {{ invitation.cited_in_submission.author_list }},
-    </p>
-    <p>
-        which you can find online at the <a href="https://scipost.org/{{ invitation.cited_in_submission.get_absolute_url }}"> submission's page</a>.
-    </p>
-    <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>
 
-    <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_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.get_title_display }} {{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}
-    </p>
+{% elif invitation.invitation_type == 'C' %}
+    {% 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>
 
-{% elif invitation.invitation_type == 'cp' %}
-    <p>
-        Your work has been cited in a paper published by SciPost,
-    </p>
-    <p>
-        {{ invitation.cited_in_publication.title }}
-        <br>
-        <i>by {{ invitation.cited_in_publication.author_list }}</i>
-        <br>
-        (published as <a href="https://scipost.org/{{ invitation.cited_in_publication.doi_label }}">{{ invitation.cited_in_publication.citation }}</a>).
-    </p>
-    <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>
+        <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 %}
 
-    <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_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.get_title_display }} {{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}
-    </p>
-{% elif invitation.invitation_type == 'C' %}
-    <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>
+    {% 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
@@ -179,7 +99,7 @@ Dear {% if invitation.message_style == 'F' %}{{ invitation.get_title_display }}
         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_key }}">registration form</a>
+        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>
@@ -194,9 +114,10 @@ Dear {% if invitation.message_style == 'F' %}{{ invitation.get_title_display }}
     <p>
         Many thanks in advance for taking a few minutes to look into it,
         <br>
-        On behalf of the SciPost Foundation,<br>
+        On behalf of the SciPost Foundation,
+        <br>
         <br>
-        {{ invitation.invited_by.get_title_display }} {{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}
+        {{ invitation.invited_by.contributor.get_title_display }} {{ invitation.invited_by.first_name }} {{ invitation.invited_by.last_name }}
     </p>
 {% elif invitation.invitation_type == 'F' %}
     <p>
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 000000000..586e34be8
--- /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 000000000..0d280a9cf
--- /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 1456cda99..c5c8e849c 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
-- 
GitLab