diff --git a/finances/admin.py b/finances/admin.py
index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..d8d4d52c35c2131bf14a5313cff84015f49881a7 100644
--- a/finances/admin.py
+++ b/finances/admin.py
@@ -1,3 +1,6 @@
 from django.contrib import admin
 
-# Register your models here.
+from .models import WorkLog
+
+
+admin.site.register(WorkLog)
diff --git a/finances/forms.py b/finances/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..36f2907125b04a5328016a652614a75f37e919b9
--- /dev/null
+++ b/finances/forms.py
@@ -0,0 +1,69 @@
+import datetime
+
+from django import forms
+from django.contrib.auth import get_user_model
+from django.utils.dates import MONTHS
+from django.db.models import Sum
+
+from .models import WorkLog
+
+today = datetime.datetime.today()
+
+
+class WorkLogForm(forms.ModelForm):
+    def __init__(self, *args, **kwargs):
+        self.types = kwargs.pop('log_types', False)
+        super().__init__(*args, **kwargs)
+        if self.types:
+            self.fields['log_type'] = forms.ChoiceField(choices=self.types)
+
+    class Meta:
+        model = WorkLog
+        fields = (
+            'comments',
+            'log_type',
+            'duration',
+        )
+        widgets = {
+            'comments': forms.Textarea(attrs={'rows': 4}),
+            'duration': forms.TextInput(attrs={'placeholder': 'HH:MM:SS'})
+        }
+
+
+class LogsMonthlyActiveFilter(forms.Form):
+    month = forms.ChoiceField(choices=[(k, v) for k, v in MONTHS.items()])
+    year = forms.ChoiceField(choices=[(y, y) for y in reversed(range(today.year-6, today.year+1))])
+
+    def __init__(self, *args, **kwargs):
+        if not kwargs.get('data', False) and not args[0]:
+            args = list(args)
+            args[0] = {
+                'month': today.month,
+                'year': today.year
+            }
+            args = tuple(args)
+        kwargs['initial'] = {
+            'month': today.month,
+            'year': today.year
+        }
+        super().__init__(*args, **kwargs)
+
+    def get_totals(self):
+        # Make accessible without need to explicitly check validity of form.
+        self.is_valid()
+
+        users = get_user_model().objects.filter(
+            work_logs__work_date__month=self.cleaned_data['month'],
+            work_logs__work_date__year=self.cleaned_data['year']).distinct()
+        output = []
+        for user in users:
+            logs = user.work_logs.filter(
+                work_date__month=self.cleaned_data['month'],
+                work_date__year=self.cleaned_data['year'])
+            output.append({
+                'logs': logs,
+                'duration': logs.aggregate(total=Sum('duration')),
+                'user': user
+            })
+
+        return output
diff --git a/finances/migrations/0001_initial.py b/finances/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0b67974f46214763138959424ed10bb21f5ef96
--- /dev/null
+++ b/finances/migrations/0001_initial.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-07 11:49
+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):
+
+    initial = True
+
+    dependencies = [
+        ('contenttypes', '0002_remove_content_type_name'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='WorkLog',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('comments', models.TextField(blank=True)),
+                ('duration', models.DurationField(blank=True, null=True)),
+                ('work_date', models.DateField(default=django.utils.timezone.now)),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('target_object_id', models.PositiveIntegerField(blank=True, null=True)),
+                ('target_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='work_log_target', to='contenttypes.ContentType')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='worklogs', to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'default_related_name': 'worklogs',
+            },
+        ),
+    ]
diff --git a/finances/migrations/0002_auto_20171007_1349.py b/finances/migrations/0002_auto_20171007_1349.py
new file mode 100644
index 0000000000000000000000000000000000000000..01d4dcaf94b2c414dfe25f2b5ddc40088e2aecbf
--- /dev/null
+++ b/finances/migrations/0002_auto_20171007_1349.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-07 11:50
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('finances', '0001_initial'),
+        ('production', '0028_auto_20171007_1311'),
+    ]
+
+    operations = []
diff --git a/finances/migrations/0003_auto_20171007_1425.py b/finances/migrations/0003_auto_20171007_1425.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d667cb908443488220eeff36b3cecac147926e5
--- /dev/null
+++ b/finances/migrations/0003_auto_20171007_1425.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-07 12:25
+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 = [
+        ('contenttypes', '0002_remove_content_type_name'),
+        ('finances', '0002_auto_20171007_1349'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='worklog',
+            old_name='target_object_id',
+            new_name='object_id',
+        ),
+        migrations.RenameField(
+            model_name='worklog',
+            old_name='target_content_type',
+            new_name='content_type',
+        ),
+        migrations.AlterField(
+            model_name='worklog',
+            name='user',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='work_logs', to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/finances/migrations/0004_auto_20171007_1426.py b/finances/migrations/0004_auto_20171007_1426.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d039886b918bc5e673be76bb82caaf1e4bb3e0a
--- /dev/null
+++ b/finances/migrations/0004_auto_20171007_1426.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-07 12:26
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('finances', '0003_auto_20171007_1425'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='worklog',
+            name='content_type',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='work_logs', to='contenttypes.ContentType'),
+        ),
+    ]
diff --git a/finances/migrations/0005_auto_20171010_0921.py b/finances/migrations/0005_auto_20171010_0921.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3ffa146363d51e1864ceca81cb91530799c0d09
--- /dev/null
+++ b/finances/migrations/0005_auto_20171010_0921.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-10 07:21
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('finances', '0004_auto_20171007_1426'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='worklog',
+            options={'ordering': ['-work_date', 'created']},
+        ),
+        migrations.AddField(
+            model_name='worklog',
+            name='log_type',
+            field=models.CharField(blank=True, max_length=128),
+        ),
+    ]
diff --git a/finances/migrations/0006_auto_20171010_1003.py b/finances/migrations/0006_auto_20171010_1003.py
new file mode 100644
index 0000000000000000000000000000000000000000..2789ccec488ef8f80370306548c28cdda16eaf2c
--- /dev/null
+++ b/finances/migrations/0006_auto_20171010_1003.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-10 08:03
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+def move_hour_registrations(apps, schema_editor):
+    """
+    Move all ProductionEvent hours to Finances model.
+    """
+    ProductionEvent = apps.get_model('production', 'ProductionEvent')
+    WorkLog = apps.get_model('finances', 'WorkLog')
+    ContentType = apps.get_model('contenttypes', 'ContentType')
+    stream_content_type = ContentType.objects.get(app_label='production', model='productionstream')
+
+    for event in ProductionEvent.objects.filter(duration__isnull=False):
+        if event.event in ['assigned_to_supervisor',
+                           'message_edadmin_to_supervisor',
+                           'message_supervisor_to_edadmin',
+                           'officer_tasked_with_proof_production',
+                           'message_supervisor_to_officer',
+                           'proofs_checked_by_supervisor',
+                           'proofs_returned_by_authors',
+                           'proofs_returned_by_authors',
+                           'authors_have_accepted_proofs',
+                           'paper_published']:
+            _type = 'Production: Supervisory tasks'
+        else:
+            _type = 'Production: Production Officer tasks'
+
+        log = WorkLog(
+            user=event.noted_by.user,
+            log_type=_type,
+            comments=event.comments,
+            duration=event.duration,
+            work_date=event.noted_on,
+            content_type=stream_content_type,
+            object_id=event.stream.id
+        )
+        log.save()
+    return
+
+
+def move_hour_registrations_inverse(apps, schema_editor):
+    """
+    Move all ProductionEvent hours to Finances model inversed (not implemented).
+    """
+    return
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('finances', '0005_auto_20171010_0921'),
+    ]
+
+    operations = [
+        migrations.RunPython(move_hour_registrations, move_hour_registrations_inverse)
+    ]
diff --git a/finances/models.py b/finances/models.py
index 71a836239075aa6e6e4ecb700e9c42c95c022d91..3982eb14a3e5b0536d504ba4ef13ffa4103d95a6 100644
--- a/finances/models.py
+++ b/finances/models.py
@@ -1,3 +1,32 @@
+from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes.fields import GenericForeignKey
 from django.db import models
+from django.utils import timezone
 
-# Create your models here.
+from .utils import id_to_slug
+
+
+class WorkLog(models.Model):
+    user = models.ForeignKey(settings.AUTH_USER_MODEL)
+    comments = models.TextField(blank=True)
+    log_type = models.CharField(max_length=128, blank=True)
+    duration = models.DurationField(blank=True, null=True)
+    work_date = models.DateField(default=timezone.now)
+    created = models.DateTimeField(auto_now_add=True)
+
+    content_type = models.ForeignKey(ContentType, blank=True, null=True)
+    object_id = models.PositiveIntegerField(blank=True, null=True)
+    content = GenericForeignKey()
+
+    class Meta:
+        default_related_name = 'work_logs'
+        ordering = ['-work_date', 'created']
+
+    def __str__(self):
+        return 'Log of {0} {1} on {2}'.format(
+            self.user.first_name, self.user.last_name, self.work_date)
+
+    @property
+    def slug(self):
+        return id_to_slug(self.id)
diff --git a/finances/templates/finances/timesheets.html b/finances/templates/finances/timesheets.html
index 63d43519a766bf7535bedf40f08346554eb7c837..f32ae41dfa46ac45e19d96ffca47e1e1e01e030f 100644
--- a/finances/templates/finances/timesheets.html
+++ b/finances/templates/finances/timesheets.html
@@ -7,6 +7,7 @@
 {% block pagetitle %}: Team Timesheets{% endblock pagetitle %}
 
 {% load bootstrap %}
+{% load scipost_extras %}
 
 {% block content %}
 
@@ -25,26 +26,25 @@
     <div class="col-12">
         <h2 class="mb-2 mt-4">Team Timesheets</h2>
         {% for total in totals %}
-            <h3 class="mb-1">{{ total.user }}</h3>
+            <h3 class="mb-1">{{ total.user.first_name }} {{ total.user.last_name }}</h3>
             <table class="table">
                 <thead class="thead-default">
                   <tr>
                       <th>Date</th>
-                      <th>By</th>
-                      <th>Stream</th>
-                      <th>Event</th>
+                      <th>Related to object</th>
+                      <th>Log type</th>
+                      <th>Comments</th>
                       <th>Duration</th>
                   </tr>
                 </thead>
-
                 <tbody>
-                    {% for event in total.events %}
+                    {% for log in total.logs %}
                         <tr>
-                            <td>{{ event.noted_on }}</td>
-                            <td>{{ event.noted_by }}</td>
-                            <td>{{ event.stream }}</td>
-                            <td>{{ event.get_event_display }}</td>
-                            <td>{{ event.duration }}</td>
+                            <td>{{ log.work_date }}</td>
+                            <td>{{ log.content }}</td>
+                            <td>{{ log.log_type }}</td>
+                            <td>{{ log.comments }}</td>
+                            <td>{{ log.duration|duration }}</td>
                         </tr>
                     {% endfor %}
                     <tr>
diff --git a/finances/templates/finances/worklog_confirm_delete.html b/finances/templates/finances/worklog_confirm_delete.html
new file mode 100644
index 0000000000000000000000000000000000000000..cc03bcc7db216d340cdee18bff64e842a149e612
--- /dev/null
+++ b/finances/templates/finances/worklog_confirm_delete.html
@@ -0,0 +1,49 @@
+{% extends 'production/base.html' %}
+
+{% load scipost_extras %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <span class="breadcrumb-item">Delete log</span>
+{% endblock %}
+
+{% load bootstrap %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Delete log</h1>
+        {% include 'submissions/_submission_card_content_sparse.html' with submission=object.content.submission %}
+    </div>
+</div>
+<div class="row">
+  <div class="col-12">
+      <form method="post">
+        {% csrf_token %}
+            <h3 class="mb-2">Are you sure you want to delete this work log?</h3>
+            <table class="table">
+                <tr>
+                    <th>Logged by</th>
+                    <td>{{ object.user }}</td>
+                </tr>
+                <tr>
+                    <th>Comment</th>
+                    <td>{{ object.comments|default:'-' }}</td>
+                </tr>
+                <tr>
+                    <th>Logged time</th>
+                    <td>{{ object.duration|duration }}</td>
+                </tr>
+                <tr>
+                    <th>Log date</th>
+                    <td>{{ object.work_date }}</td>
+                </tr>
+            </table>
+            <input type="submit" class="btn btn-danger" value="Yes, delete log" />
+      </form>
+    </ul>
+  </div>
+</div>
+
+{% endblock content %}
diff --git a/finances/templates/partials/finances/logs.html b/finances/templates/partials/finances/logs.html
new file mode 100644
index 0000000000000000000000000000000000000000..bd3f4c9d5d1400f3254166f9899b64a36a70eeff
--- /dev/null
+++ b/finances/templates/partials/finances/logs.html
@@ -0,0 +1,29 @@
+ {% load scipost_extras %}
+
+ <ul class="list-unstyled">
+  {% for log in logs %}
+        <li id="log_{{ log.slug }}" class="pb-2">
+            <div class="d-flex justify-content-between">
+                <div>
+                    <strong>{{ log.user.first_name }} {{ log.user.last_name }}</strong>
+                    <br>
+                    <span class="text-muted">{{ log.log_type }}</span>
+                    <br>
+                    {{ log.comments|linebreaksbr }}
+                </div>
+                <div class="text-muted text-right d-flex justify-content-end">
+                    <div>
+                        {{ log.work_date }}
+                        <br>
+                        <strong>Duration: {{ log.duration|duration }}</strong>
+                    </div>
+                    <div class="pl-2">
+                        <a class="text-danger" href="{% url 'finances:log_delete' log.slug %}"><i class="fa fa-trash" aria-hidden="true"></i></a>
+                    </div>
+                </div>
+            </div>
+        </li>
+    {% empty %}
+        <li>No logs were found.</li>
+    {% endfor %}
+</ul>
diff --git a/finances/urls.py b/finances/urls.py
index 21569e295d98c0c64a768ff55dc9c71bcbf07e7e..80346704ba12068b5150c4005395c29c3111effd 100644
--- a/finances/urls.py
+++ b/finances/urls.py
@@ -5,4 +5,5 @@ from . import views
 urlpatterns = [
     url(r'^$', views.timesheets, name='finance'),
     url(r'^timesheets$', views.timesheets, name='timesheets'),
+    url(r'^logs/(?P<slug>\d+)/delete$', views.LogDeleteView.as_view(), name='log_delete'),
 ]
diff --git a/finances/utils.py b/finances/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7edcfab36651b6f6948eb299393963c27473d2e
--- /dev/null
+++ b/finances/utils.py
@@ -0,0 +1,7 @@
+
+def id_to_slug(id):
+    return max(0, int(id) + 821)
+
+
+def slug_to_id(slug):
+    return max(0, int(slug) - 821)
diff --git a/finances/views.py b/finances/views.py
index b967443f18581bfd581f6710541e6f73e46fbf4f..33200e7c2eb3a10a9c2b6f905960d0cb497583f9 100644
--- a/finances/views.py
+++ b/finances/views.py
@@ -1,7 +1,12 @@
+from django.contrib import messages
 from django.contrib.auth.decorators import permission_required
+from django.http import Http404
 from django.shortcuts import render
+from django.views.generic.edit import DeleteView
 
-from production.forms import ProductionUserMonthlyActiveFilter
+from .forms import LogsMonthlyActiveFilter
+from .models import WorkLog
+from .utils import slug_to_id
 
 
 @permission_required('scipost.can_view_timesheets', raise_exception=True)
@@ -9,7 +14,7 @@ def timesheets(request):
     """
     See an overview per month of all timesheets.
     """
-    form = ProductionUserMonthlyActiveFilter(request.GET or None)
+    form = LogsMonthlyActiveFilter(request.GET or None)
     context = {
         'form': form,
     }
@@ -18,3 +23,17 @@ def timesheets(request):
     context['totals'] = form.get_totals()
 
     return render(request, 'finances/timesheets.html', context)
+
+
+class LogDeleteView(DeleteView):
+    model = WorkLog
+
+    def get_object(self):
+        try:
+            return WorkLog.objects.get(user=self.request.user, id=slug_to_id(self.kwargs['slug']))
+        except WorkLog.DoesNotExist:
+            raise Http404
+
+    def get_success_url(self):
+        messages.success(self.request, 'Log deleted.')
+        return self.object.content.get_absolute_url()
diff --git a/journals/views.py b/journals/views.py
index fb5245c315d70fd7a832e0720739ad1e19e835fc..4d9c2e619d662ed9c0eefcfdfc454d69a3caef4a 100644
--- a/journals/views.py
+++ b/journals/views.py
@@ -30,6 +30,9 @@ from comments.models import Comment
 from funders.models import Funder
 from submissions.models import Submission, Report
 from scipost.models import Contributor
+from production.constants import PROOFS_PUBLISHED
+from production.models import ProductionEvent
+from production.signals import notify_stream_status_change
 
 from funders.forms import FunderSelectForm, GrantSelectForm
 from scipost.forms import ConfirmationForm
@@ -264,6 +267,21 @@ def validate_publication(request):
         submission.status = 'published'
         submission.save()
 
+        # Update ProductionStream
+        stream = submission.production_stream
+        if stream:
+            stream.status = PROOFS_PUBLISHED
+            stream.save()
+            if request.user.production_user:
+                prodevent = ProductionEvent(
+                    stream=stream,
+                    event='status',
+                    comments=' published the manuscript.',
+                    noted_by=request.user.production_user
+                )
+                prodevent.save()
+            notify_stream_status_change(request.user, stream, False)
+
         # TODO: Create a Commentary Page
         # Email authors
         JournalUtils.load({'publication': publication})
diff --git a/mails/forms.py b/mails/forms.py
index 2c34d6453d48abdd7803fb433788d84e8040437c..c277051d2d46c036c918608a1148e787da8a2115 100644
--- a/mails/forms.py
+++ b/mails/forms.py
@@ -1,62 +1,127 @@
 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 .widgets import SummernoteEditor
+
 
 class EmailTemplateForm(forms.Form):
     subject = forms.CharField(max_length=250, label="Subject*")
-    text = forms.CharField(widget=forms.Textarea(attrs={'rows': 25}), label="Text*")
+    text = forms.CharField(widget=SummernoteEditor, label="Text*")
     extra_recipient = forms.EmailField(label="Optional: bcc this email to", required=False)
 
     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.txt' % self.mail_code)
-        self.mail_template = mail_template.render(kwargs)
+        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'))
-        recipient = self.object
-        for attr in self.mail_data.get('to_address').split('.'):
-            recipient = getattr(recipient, attr)
-        self.recipient = recipient
+        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
+
+        if not self.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 = self.mail_template
+        self.fields['text'].initial = mail_template
         self.fields['subject'].initial = self.mail_data['subject']
 
-    def send(self):
+    def save_data(self):
         # Get text and html
-        message = self.cleaned_data['text']
-        html_template = loader.get_template('email/general.html')
-        html_message = html_template.render({'text': message})
-
-        # Get recipients list. Always send through BCC to prevent privacy issues!
-        bcc_to = self.object
-        for attr in self.mail_data.get('bcc_to').split('.'):
-            bcc_to = getattr(bcc_to, attr)
-        bcc_list = [
-            bcc_to,
-        ]
-        if self.cleaned_data.get('additional_bcc'):
-            bcc_list.append(self.cleaned_data.get('additional_bcc'))
+        html_message = self.cleaned_data['text']
+        handler = HTML2Text()
+        message = handler.handle(html_message)
+
+        # Get recipients list. Try to send through BCC to prevent privacy issues!
+        bcc_list = []
+        if self.mail_data.get('bcc_to') and self.object:
+            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
 
+        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:
+            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,
+        }
+
+    def clean(self):
+        data = super().clean()
+        self.save_data()
+        return data
+
+    def send(self):
         # Send the mail
         email = EmailMultiAlternatives(
-            self.cleaned_data['subject'],
-            message,
+            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.recipient],  # To
-            bcc=bcc_list,
+            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(html_message, 'text/html')
+        email.attach_alternative(self.mail_fields['html_message'], 'text/html')
         email.send(fail_silently=False)
diff --git a/mails/templates/mail_templates/partners_followup_mail.html b/mails/templates/mail_templates/partners_followup_mail.html
new file mode 100644
index 0000000000000000000000000000000000000000..bceea0343f05111cff171992dfa23f7d7b36dab7
--- /dev/null
+++ b/mails/templates/mail_templates/partners_followup_mail.html
@@ -0,0 +1,42 @@
+<p>
+    Dear {{ contact.get_title_display }} {{ contact.last_name }},
+</p>
+
+<p>
+    We recently contacted you concerning SciPost, and to probe your interest in joining its Supporting Partners Board. With this follow-up email, I would simply like to check whether you got the original message.
+</p>
+
+<p>
+    <a href="https://scipost.org">SciPost</a> is a next-generation publication portal aiming to transform the business of scientific publishing. You can find a one-page summary in <a href="https://scipost.org/static/scipost/SPB/SciPost_Supporting_Partners_Board_Prospectus.pdf">our online prospectus</a> outlining the reasons why joining would be beneficial for your institution.
+</p>
+
+<p>
+    I will be happy to provide any required further details. If you are interested, you can simply get in touch via this address (partners@scipost.org). I sincerely hope that SciPost will be able to count on your support.
+</p>
+
+<p>
+    If you not the right representative to get in touch with, could you please forward this to the right person, or let us know?
+</p>
+
+<p>
+    Many thanks in advance,
+</p>
+
+<p>
+    On behalf of the SciPost Foundation,<br><br>
+    Prof. dr Jean-Sébastien Caux<br>
+    J.S.Caux@uva.nl<br>
+    http://jscaux.org<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>
+
+{% include 'email/_footer.html' %}
diff --git a/mails/templates/mail_templates/partners_followup_mail.json b/mails/templates/mail_templates/partners_followup_mail.json
new file mode 100644
index 0000000000000000000000000000000000000000..efb95bbc8882bb10b321d44ed84cb72bcede49a0
--- /dev/null
+++ b/mails/templates/mail_templates/partners_followup_mail.json
@@ -0,0 +1,7 @@
+{
+    "subject": "SciPost: Supporting Partners Board",
+    "to_address": "email",
+    "from_address_name": "SciPost Supporting Partners",
+    "from_address": "partners@scipost.org",
+    "context_object": "contact"
+}
diff --git a/mails/templates/mail_templates/partners_initial_mail.html b/mails/templates/mail_templates/partners_initial_mail.html
new file mode 100644
index 0000000000000000000000000000000000000000..2645aa22ef54378ad35d1cce071ab9a60139fa54
--- /dev/null
+++ b/mails/templates/mail_templates/partners_initial_mail.html
@@ -0,0 +1,53 @@
+<p>
+    Dear {{ contact.get_title_display }} {{ contact.last_name }},
+</p>
+
+<p>
+    You might by now have heard of SciPost, a recently-launched initiative aiming to bring disruptive change to current academic publishing practices.
+</p>
+
+<p>
+    In summary, SciPost 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.
+    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 page</a> and <a href="https://scipost.org/FAQ">our FAQ</a>.
+</p>
+
+<p>
+    Crucially, as explained on our <a href="https://scipost.org/partners">Partners page</a>, SciPost follows a completely different funding model than traditional publishers, and provides a cost-slashing alternative to existing platforms. SciPost charges neither subscription fees, nor article processing charges; its activities are instead to be collectively financed through a Supporting Partners Board, formed by a worldwide consortium of institutions and organizations which directly or indirectly benefit from SciPost’s activities.
+</p>
+
+<p>
+    A short summary of important aspects of SciPost and reasons for you to join its Supporting Partners Board are given in <a href="https://scipost.org/static/scipost/SPB/SciPost_Supporting_Partners_Board_Prospectus.pdf">our one-page prospectus</a>.
+</p>
+
+<p>
+    Support takes the form of a small financial commitment, collectively pooled to enable SciPost to perform all its publication-related activities, maintain its online portal and implement its long-term development plan.
+</p>
+
+<p>
+    In <a href="https://scipost.org/static/scipost/SPB/SciPost_Supporting_Partner_Agreement.pdf">the agreement template</a>, you will find many more specific details about our operations, requirements and funding strategy. I would greatly appreciate if you took a few minutes to read through this document.
+</p>
+
+<p>
+    It would be a privilege to welcome you as members of our Supporting Partners Board. I am hereby contacting you to enquire whether your institution would consider joining. Your support at this time is crucially required to make our initiative sustainable, and to help make it possible for the community to reap all the benefits deriving form its viable implementation.
+</p>
+
+<p>
+    I will be happy to provide any required further details. If you are interested, you can simply get in touch via this address (partners@scipost.org). I sincerely hope that SciPost will be able to count on your support.
+</p>
+
+<p>
+    On behalf of the SciPost Foundation,<br><br>
+    Prof. dr Jean-Sébastien Caux<br>
+    J.S.Caux@uva.nl<br>
+    http://jscaux.org<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>
diff --git a/mails/templates/mail_templates/partners_initial_mail.json b/mails/templates/mail_templates/partners_initial_mail.json
new file mode 100644
index 0000000000000000000000000000000000000000..efb95bbc8882bb10b321d44ed84cb72bcede49a0
--- /dev/null
+++ b/mails/templates/mail_templates/partners_initial_mail.json
@@ -0,0 +1,7 @@
+{
+    "subject": "SciPost: Supporting Partners Board",
+    "to_address": "email",
+    "from_address_name": "SciPost Supporting Partners",
+    "from_address": "partners@scipost.org",
+    "context_object": "contact"
+}
diff --git a/mails/templates/mail_templates/production_send_proofs.html b/mails/templates/mail_templates/production_send_proofs.html
new file mode 100644
index 0000000000000000000000000000000000000000..bfd87c4e2ed0ed652612358f0168b18a073c2c4e
--- /dev/null
+++ b/mails/templates/mail_templates/production_send_proofs.html
@@ -0,0 +1,20 @@
+<p>
+    Dear {% for author in proofs.stream.submission.authors.all %}{{ author.get_title_display }} {{ author.user.last_name }}{% if not forloop.last %}, {% elif proofs.stream.submission.authors.count > 1 %} and {% endif %}{% endfor %},
+</p>
+
+<p>
+    The SciPost production team has finished the proofs of your manuscript (version {{ proofs.version }}). You can find the proofs on your <a href="https://scipost.org{{ proofs.stream.submission.get_absolute_url }}">submission's page</a>.
+</p>
+
+<p>
+    Please review the proofs and let us know whether you accept the proofs for publication using the form on the submission page.
+</p>
+
+<p>
+    Sincerely,<br>
+
+    {{ proofs.stream.supervisor.user.first_name }} {{ proofs.stream.supervisor.user.last_name }},<br>
+    SciPost Production
+</p>
+
+{% include 'email/_footer.html' %}
diff --git a/mails/templates/mail_templates/production_send_proofs.json b/mails/templates/mail_templates/production_send_proofs.json
new file mode 100644
index 0000000000000000000000000000000000000000..5e38b3a6e8bb1f858e66805c7cfd09da77e93913
--- /dev/null
+++ b/mails/templates/mail_templates/production_send_proofs.json
@@ -0,0 +1,7 @@
+{
+    "subject": "SciPost: Your proofs have been produced",
+    "to_address": "stream.submission.authors.all",
+    "from_address_name": "SciPost Production",
+    "from_address": "proofs@scipost.org",
+    "context_object": "proofs"
+}
diff --git a/mails/templates/mail_templates/submissions_referee_invite.html b/mails/templates/mail_templates/submissions_referee_invite.html
new file mode 100644
index 0000000000000000000000000000000000000000..2c2bd80595864b3e4319ea9d3586af21533b0b62
--- /dev/null
+++ b/mails/templates/mail_templates/submissions_referee_invite.html
@@ -0,0 +1,29 @@
+<p>
+    Dear {{invitation.referee.get_title_display}} {{invitation.referee.user.last_name}},
+</p>
+
+<p>
+    We have received a Submission to SciPost which, in view of your expertise and on behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.user.last_name}}, we would like to invite you to referee:
+    <br>
+    <a href="https://scipost.org{{invitation.submission.get_absolute_url}}">{{invitation.submission.title}} by {{invitation.submission.author_list}}</a>.
+</p>
+
+<p>
+    Please <a href="https://scipost.org/submissions/accept_or_decline_ref_invitations">accept or decline</a> (login required) this invitation as soon as possible (ideally within the next 2 days).
+</p>
+
+<p>
+    If you accept, your report can be submitted by simply clicking on the "Contribute a Report" link on <a href="https://scipost.org{{invitation.submission.get_absolute_url}}">the Submission Page</a> before the reporting deadline (currently set at {{invitation.submission.reporting_deadline|date:'d-m-Y'}}; your report will be automatically recognized as an invited report).
+</p>
+
+<p>
+    You might want to make sure you are familiar with our refereeing <a href="https://scipost.org/journals/journals_terms_and_conditions">code of conduct</a> and with <a href="https://scipost.org/submissions/sub_and_ref_procedure">the refereeing procedure</a>.
+</p>
+
+<p>
+    We would be extremely grateful for your contribution, and thank you in advance for your consideration.
+    <br>
+    The SciPost Team.
+</p>
+
+{% include 'email/_footer.html' %}
diff --git a/mails/templates/mail_templates/submissions_referee_invite.txt b/mails/templates/mail_templates/submissions_referee_invite.txt
deleted file mode 100644
index c831966f4a010d033bc0c564381525ce25a7e61c..0000000000000000000000000000000000000000
--- a/mails/templates/mail_templates/submissions_referee_invite.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-Dear {{invitation.referee.get_title_display}} {{invitation.referee.user.last_name}},
-
-We have received a Submission to SciPost which, in view of your expertise and on behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.user.last_name}}, we would like to invite you to referee:
-
-{{invitation.submission.title}} by {{invitation.submission.author_list}} (see https://scipost.org{{invitation.submission.get_absolute_url}}).
-
-Please visit https://scipost.org/submissions/accept_or_decline_ref_invitations (login required) as soon as possible (ideally within the next 2 days) in order to accept or decline this invitation.
-
-If you accept, your report can be submitted by simply clicking on the "Contribute a Report" link at https://scipost.org{{invitation.submission.get_absolute_url}} before the reporting deadline (currently set at {{invitation.submission.reporting_deadline|date:'d-m-Y'}}; your report will be automatically recognized as an invited report).
-
-You might want to make sure you are familiar with our refereeing code of conduct https://scipost.org/journals/journals_terms_and_conditions and with the refereeing procedure https://scipost.org/submissions/sub_and_ref_procedure.
-
-We would be extremely grateful for your contribution, and thank you in advance for your consideration.
-
-The SciPost Team.
diff --git a/mails/templates/mails/mail_form.html b/mails/templates/mails/mail_form.html
index b2837b64a570490b07e399aa2634349f301cce06..fbb170d4dcffc6286e03a3737fdc24d8eef863dc 100644
--- a/mails/templates/mails/mail_form.html
+++ b/mails/templates/mails/mail_form.html
@@ -11,7 +11,7 @@
 
     <form enctype="multipart/form-data" method="post">
         {% csrf_token %}
-        {{form|bootstrap}}
+        {{ form|bootstrap }}
         <div class="form-group row">
             <div class="offset-md-2 col-md-10">
                 <input class="btn btn-secondary mr-2" type="reset" value="Reset to default">
@@ -20,3 +20,9 @@
         </div>
     </form>
 {% endblock content %}
+
+
+{% block footer_script %}
+    {{ block.super }}
+    {{ form.media }}
+{% endblock footer_script %}
diff --git a/mails/views.py b/mails/views.py
index 04dce36dd4b9f3166e16b80893443a590158d338..f7fedc01ded99d81c6f90b0d33f17e8489024ca9 100644
--- a/mails/views.py
+++ b/mails/views.py
@@ -10,6 +10,10 @@ class MailEditingSubView(object):
         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 is_valid(self):
         return self.mail_form.is_valid()
 
diff --git a/mails/widgets.py b/mails/widgets.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5f1aff3003b38e75cacf58fc5b279304e883491
--- /dev/null
+++ b/mails/widgets.py
@@ -0,0 +1,72 @@
+import json
+
+from django.core.urlresolvers import reverse, NoReverseMatch
+from django.forms import widgets, Media
+from django.utils.safestring import mark_safe
+# from django.conf import settings
+
+# from . import PLUGINS, PLUGINS_WITH_CSS
+
+
+class SummernoteEditor(widgets.Textarea):
+    def __init__(self, *args, **kwargs):
+        self.options = kwargs.pop('options', {})
+        self.include_jquery = False
+        super().__init__(*args, **kwargs)
+
+    def get_options(self):
+
+        default_options = {
+            'inlineMode': False,
+            'toolbar': [
+                ['style', ['bold', 'italic', 'underline', 'clear']],
+                ['font', ['strikethrough', 'superscript', 'subscript']],
+                ['fontsize', ['fontsize']],
+                ['para', ['ul', 'ol', 'paragraph']],
+            ],
+        }
+
+        try:
+            file_upload_url = reverse('froala_editor_file_upload')
+            default_options['fileUploadURL'] = file_upload_url
+            default_options.update([
+                ('fileUploadParams', {'csrfmiddlewaretoken': 'csrftokenplaceholder'})])
+        except NoReverseMatch:
+            default_options['fileUpload'] = False
+
+        # settings_options = getattr(settings, 'FROALA_EDITOR_OPTIONS', {})
+        # options = dict(default_options.items() + settings_options.items() + self.options.items())
+        options = dict(default_options.items()).copy()
+        # options.update(settings_options.items())
+        options.update(self.options.items())
+
+        json_options = json.dumps(options)
+        json_options = json_options.replace('"csrftokenplaceholder"', 'getCookie("csrftoken")')
+        return json_options
+
+    def render(self, name, value, attrs=None):
+        html = super().render(name, value, attrs)
+        el_id = self.build_attrs(attrs).get('id')
+        html += self.trigger_summernote(el_id, self.get_options())
+        return mark_safe(html)
+
+    def trigger_summernote(self, el_id, options):
+        str = """
+        <script>
+            $(function(){
+                $('#%s').summernote(%s)
+            });
+        </script>""" % (el_id, options)
+        return str
+
+    @property
+    def media(self):
+        css = {
+            'all': ('//cdnjs.cloudflare.com/ajax/libs/summernote/0.8.8/summernote-bs4.css',)
+        }
+        js = ('//cdnjs.cloudflare.com/ajax/libs/summernote/0.8.8/summernote-bs4.js',)
+
+        if self.include_jquery:
+            js = ('//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js',) + js
+
+        return Media(css=css, js=js)
diff --git a/partners/forms.py b/partners/forms.py
index 87322b2d67118443949252cdcdfd3d7e6c3d8faf..18a2df7814561a220e886717e019b8b1279a8866 100644
--- a/partners/forms.py
+++ b/partners/forms.py
@@ -458,26 +458,6 @@ class ProspectiveContactForm(forms.ModelForm):
         widgets = {'prospartner': forms.HiddenInput()}
 
 
-class EmailProspectivePartnerContactForm(forms.Form):
-    email_subject = forms.CharField(widget=forms.Textarea(),
-                                    initial='Supporting Partners Board')
-    message = forms.CharField(widget=forms.Textarea(), required=False)
-    include_SPB_summary = forms.BooleanField(
-        required=False, initial=True,
-        label='include SPB summary with message')
-
-    def __init__(self, *args, **kwargs):
-        super(EmailProspectivePartnerContactForm, self).__init__(*args, **kwargs)
-        self.fields['email_subject'].widget.attrs.update(
-            {'rows': 1})
-        self.fields['message'].widget.attrs.update(
-            {'placeholder': 'Write your message in this box (optional).'})
-
-
-class EmailProspectivePartnerGenericForm(EmailProspectivePartnerContactForm):
-    email = forms.EmailField(label='Generic address for emailing')
-
-
 class ProspectivePartnerEventForm(forms.ModelForm):
     class Meta:
         model = ProspectivePartnerEvent
diff --git a/partners/migrations/0033_auto_20170930_2230.py b/partners/migrations/0033_auto_20170930_2230.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5a91d3203db66d8420cb03e9e6d9f5eb5cd39d3
--- /dev/null
+++ b/partners/migrations/0033_auto_20170930_2230.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-30 20:30
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import scipost.storage
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('partners', '0032_auto_20170829_0727'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='partnersattachment',
+            name='attachment',
+            field=models.FileField(storage=scipost.storage.SecureFileStorage(), upload_to='UPLOADS/PARTNERS/ATTACHMENTS'),
+        ),
+    ]
diff --git a/partners/migrations/0033_auto_20171003_1512.py b/partners/migrations/0033_auto_20171003_1512.py
index e665732968b38d015b34531e21e2d8262aba9d1f..7920006ce7643d7388484c70831ee11c0eaf528d 100644
--- a/partners/migrations/0033_auto_20171003_1512.py
+++ b/partners/migrations/0033_auto_20171003_1512.py
@@ -3,7 +3,6 @@
 from __future__ import unicode_literals
 
 from django.db import migrations, models
-import partners.storage
 
 
 class Migration(migrations.Migration):
@@ -13,9 +12,4 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
-        migrations.AlterField(
-            model_name='partnersattachment',
-            name='attachment',
-            field=models.FileField(storage=partners.storage.SecureFileStorage(), upload_to='UPLOADS/PARTNERS/ATTACHMENTS'),
-        ),
     ]
diff --git a/partners/migrations/0034_merge_20171004_1946.py b/partners/migrations/0034_merge_20171004_1946.py
new file mode 100644
index 0000000000000000000000000000000000000000..872f84b7f6536230ad2273a3f11de57991712c7e
--- /dev/null
+++ b/partners/migrations/0034_merge_20171004_1946.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-04 17:46
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('partners', '0033_auto_20171003_1512'),
+        ('partners', '0033_auto_20170930_2230'),
+    ]
+
+    operations = [
+    ]
diff --git a/partners/migrations/0037_merge_20171009_2000.py b/partners/migrations/0037_merge_20171009_2000.py
new file mode 100644
index 0000000000000000000000000000000000000000..926f446e1be73cec30290f62cfe356007ad6a955
--- /dev/null
+++ b/partners/migrations/0037_merge_20171009_2000.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-09 18:00
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('partners', '0034_merge_20171004_1946'),
+        ('partners', '0036_auto_20171004_2014'),
+    ]
+
+    operations = [
+    ]
diff --git a/partners/models.py b/partners/models.py
index 6f8df9d06a6d512633687bedf36bab611b32d1b7..17b28e604efa747ea3cd0ebdd1f167d3b0cea8c2 100644
--- a/partners/models.py
+++ b/partners/models.py
@@ -26,11 +26,11 @@ from .constants import PROSPECTIVE_PARTNER_EVENT_EMAIL_SENT,\
 
 from .managers import MembershipAgreementManager, ProspectivePartnerManager, PartnerManager,\
                       ContactRequestManager, PartnersAttachmentManager
-from .storage import SecureFileStorage
 
 from scipost.constants import TITLE_CHOICES
 from scipost.fields import ChoiceArrayField
 from scipost.models import get_sentinel_user, Contributor
+from scipost.storage import SecureFileStorage
 
 
 
diff --git a/partners/templates/partners/_prospective_partner_card.html b/partners/templates/partners/_prospective_partner_card.html
index 28f587665c6f72da23ad1600889cd8ff2c17065e..5ab2d85a1d11e9292d97d516f9fac472b24f7288 100644
--- a/partners/templates/partners/_prospective_partner_card.html
+++ b/partners/templates/partners/_prospective_partner_card.html
@@ -15,7 +15,10 @@
       <p>{{ pp.get_status_display }}</p>
     </div>
     <div class="col-md-7">
-      <a href="{% url 'partners:email_prospartner_generic' prospartner_id=pp.id %}">Compose email to a generic address</a>
+        <ul>
+          <li><a href="{% url 'partners:email_prospartner_generic' prospartner_id=pp.id %}">Compose email to a generic address</a></li>
+          <li><a href="{% url 'partners:email_prospartner_generic' prospartner_id=pp.id mail='followup' %}">Compose follow-up email to a generic address</a></li>
+        </ul>
       <h3>Contacts:</h3>
       <a class="d-inline-block mb-2" href="{% url 'partners:add_prospartner_contact' prospartner_id=pp.id %}">Add a contact</a>
       <table class="table">
@@ -31,7 +34,12 @@
             <td>{{ contact.role }}</td>
             <td>{{ contact.get_title_display }} {{ contact.first_name }} {{ contact.last_name }}</td>
             <td>{{ contact.email }}</td>
-	    <td><a href="{% url 'partners:email_prospartner_contact' contact_id=contact.id %}">Compose email</a></td>
+	    <td>
+            <ul>
+                <li><a href="{% url 'partners:email_prospartner_contact' contact_id=contact.id %}">Compose email</a>
+                <li><a href="{% url 'partners:email_prospartner_contact' contact_id=contact.id mail='followup' %}">Compose follow-up email</a>
+            </ul>
+        </td>
 	  </tr>
           {% empty %}
           <tr>
diff --git a/partners/urls.py b/partners/urls.py
index f376c8de27594f1f53fa970b3d4b201c62518d90..b7d4e86380e830a36c026b713008e9b3349a29cb 100644
--- a/partners/urls.py
+++ b/partners/urls.py
@@ -13,6 +13,8 @@ urlpatterns = [
         name='add_prospective_partner'),
     url(r'^prospects/contacts/(?P<contact_id>[0-9]+)/email$',
         views.email_prospartner_contact, name='email_prospartner_contact'),
+    url(r'^prospects/contacts/(?P<contact_id>[0-9]+)/email/(?P<mail>followup)$',
+        views.email_prospartner_contact, name='email_prospartner_contact'),
 
     url(r'^prospects/(?P<prospartner_id>[0-9]+)/contacts/add$',
         views.add_prospartner_contact, name='add_prospartner_contact'),
@@ -20,6 +22,8 @@ urlpatterns = [
         views.promote_prospartner, name='promote_prospartner'),
     url(r'^prospects/(?P<prospartner_id>[0-9]+)/email_generic',
         views.email_prospartner_generic, name='email_prospartner_generic'),
+    url(r'^prospects/(?P<prospartner_id>[0-9]+)/email_generic/(?P<mail>followup)',
+        views.email_prospartner_generic, name='email_prospartner_generic'),
     url(r'^prospects/(?P<prospartner_id>[0-9]+)/events/add$',
         views.add_prospartner_event, name='add_prospartner_event'),
 
diff --git a/partners/utils.py b/partners/utils.py
index 2d5241c44c89bcd779f7f0b0e992a634b06c4b9a..809d580e46d9025249301ed625f98a4fb1cff684 100644
--- a/partners/utils.py
+++ b/partners/utils.py
@@ -5,26 +5,6 @@ class PartnerUtils(BaseMailUtil):
     mail_sender = 'partners@scipost.org'
     mail_sender_title = 'SciPost Supporting Partners'
 
-    @classmethod
-    def email_prospartner_contact(cls):
-        """
-        Email a contact of a ProspectivePartner,
-        for example to establish a first contact
-        and invite participation to the Supporting Partners Board.
-        """
-        cls._send_mail(cls, 'email_prospartner_contact',
-                       [cls._context['contact'].email],
-                       cls._context['email_subject'])
-
-    @classmethod
-    def email_prospartner_generic(cls):
-        """
-        Email a generic address for a ProspectivePartner
-        for which no Contact could be defined.
-        """
-        cls._send_mail(cls, 'email_prospartner_contact',
-                       [cls._context['email']],
-                       cls._context['email_subject'])
 
     @classmethod
     def email_contact_new_for_activation(cls, current_user):
diff --git a/partners/views.py b/partners/views.py
index 1a94818df5390b13a87b613425656fcfd2f2b4be..0acab83ef34b1bc6fcc097ad37f0c589cb0e87f9 100644
--- a/partners/views.py
+++ b/partners/views.py
@@ -10,6 +10,8 @@ from django.utils import timezone
 
 from guardian.decorators import permission_required
 
+from mails.views import MailEditingSubView
+
 from .constants import PROSPECTIVE_PARTNER_REQUESTED,\
     PROSPECTIVE_PARTNER_APPROACHED, PROSPECTIVE_PARTNER_ADDED,\
     PROSPECTIVE_PARTNER_EVENT_REQUESTED, PROSPECTIVE_PARTNER_EVENT_EMAIL_SENT
@@ -17,13 +19,12 @@ from .models import Partner, ProspectivePartner, ProspectiveContact, ContactRequ
                     ProspectivePartnerEvent, MembershipAgreement, Contact, Institution,\
                     PartnersAttachment
 from .forms import ProspectivePartnerForm, ProspectiveContactForm,\
-                   EmailProspectivePartnerContactForm, PromoteToPartnerForm,\
+                   PromoteToPartnerForm,\
                    ProspectivePartnerEventForm, MembershipQueryForm,\
                    PartnerForm, ContactForm, ContactFormset, ContactModelFormset,\
                    NewContactForm, InstitutionForm, ActivationForm, PartnerEventForm,\
                    MembershipAgreementForm, RequestContactForm, RequestContactFormSet,\
-                   ProcessRequestContactForm, PartnersAttachmentFormSet, PartnersAttachmentForm,\
-                   EmailProspectivePartnerGenericForm
+                   ProcessRequestContactForm, PartnersAttachmentFormSet, PartnersAttachmentForm
 from .utils import PartnerUtils
 
 
@@ -261,10 +262,15 @@ def add_prospartner_contact(request, prospartner_id):
 
 @permission_required('scipost.can_email_prospartner_contact', return_403=True)
 @transaction.atomic
-def email_prospartner_contact(request, contact_id):
+def email_prospartner_contact(request, contact_id, mail=None):
     contact = get_object_or_404(ProspectiveContact, pk=contact_id)
-    form = EmailProspectivePartnerContactForm(request.POST or None)
-    if form.is_valid():
+
+    if mail == 'followup':
+        code = 'partners_followup_mail'
+    else:
+        code = 'partners_initial_mail'
+    mail_request = MailEditingSubView(request, mail_code=code, contact=contact)
+    if mail_request.is_valid():
         comments = 'Email sent to %s.' % str(contact)
         prospartnerevent = ProspectivePartnerEvent(
             prospartner=contact.prospartner,
@@ -277,25 +283,26 @@ def email_prospartner_contact(request, contact_id):
                                           PROSPECTIVE_PARTNER_ADDED]:
             contact.prospartner.status = PROSPECTIVE_PARTNER_APPROACHED
             contact.prospartner.save()
-        PartnerUtils.load({'contact': contact,
-                           'email_subject': form.cleaned_data['email_subject'],
-                           'message': form.cleaned_data['message'],
-                           'include_SPB_summary': form.cleaned_data['include_SPB_summary']})
 
-        PartnerUtils.email_prospartner_contact()
-        messages.success(request, 'Email successfully sent')
+        messages.success(request, 'Email successfully sent.')
+        mail_request.send()
         return redirect(reverse('partners:dashboard'))
-    context = {'contact': contact, 'form': form}
-    return render(request, 'partners/email_prospartner_contact.html', context)
+    else:
+        return mail_request.return_render()
 
 
 @permission_required('scipost.can_email_prospartner_contact', return_403=True)
 @transaction.atomic
-def email_prospartner_generic(request, prospartner_id):
+def email_prospartner_generic(request, prospartner_id, mail=None):
     prospartner = get_object_or_404(ProspectivePartner, pk=prospartner_id)
-    form = EmailProspectivePartnerGenericForm(request.POST or None)
-    if form.is_valid():
-        comments = 'Email sent to %s.' % form.cleaned_data['email']
+
+    if mail == 'followup':
+        code = 'partners_followup_mail'
+    else:
+        code = 'partners_initial_mail'
+    mail_request = MailEditingSubView(request, mail_code=code)
+    if mail_request.is_valid():
+        comments = 'Email sent to %s.' % str(mail_request.recipients_string)
         prospartnerevent = ProspectivePartnerEvent(
             prospartner=prospartner,
             event=PROSPECTIVE_PARTNER_EVENT_EMAIL_SENT,
@@ -307,17 +314,12 @@ def email_prospartner_generic(request, prospartner_id):
                                   PROSPECTIVE_PARTNER_ADDED]:
             prospartner.status = PROSPECTIVE_PARTNER_APPROACHED
             prospartner.save()
-        PartnerUtils.load({'institution_name': prospartner.institution_name,
-                           'email': form.cleaned_data['email'],
-                           'email_subject': form.cleaned_data['email_subject'],
-                           'message': form.cleaned_data['message'],
-                           'include_SPB_summary': form.cleaned_data['include_SPB_summary']})
-
-        PartnerUtils.email_prospartner_generic()
-        messages.success(request, 'Email successfully sent')
+
+        messages.success(request, 'Email successfully sent.')
+        mail_request.send()
         return redirect(reverse('partners:dashboard'))
-    context = {'prospartner': prospartner, 'form': form}
-    return render(request, 'partners/email_prospartner_generic.html', context)
+    else:
+        return mail_request.return_render()
 
 
 @permission_required('scipost.can_manage_SPB', return_403=True)
diff --git a/production/admin.py b/production/admin.py
index 76c82159421864bfaedab50380d7221fc0cd2298..2880659cd81d1d9efe90260d12ff1c7c9bbc23b5 100644
--- a/production/admin.py
+++ b/production/admin.py
@@ -2,7 +2,7 @@ from django.contrib import admin
 
 from guardian.admin import GuardedModelAdmin
 
-from .models import ProductionStream, ProductionEvent, ProductionUser, ProductionEvent
+from .models import ProductionStream, ProductionEvent, ProductionUser, Proofs
 
 
 def event_count(obj):
@@ -30,6 +30,12 @@ class ProductionStreamAdmin(GuardedModelAdmin):
     )
 
 
+class ProductionProofsAdmin(admin.ModelAdmin):
+    list_display = ['stream', 'version', 'status', 'accessible_for_authors']
+    list_filter = ['status', 'accessible_for_authors']
+
+
+admin.site.register(Proofs, ProductionProofsAdmin)
 admin.site.register(ProductionUser)
 admin.site.register(ProductionEvent)
 admin.site.register(ProductionStream, ProductionStreamAdmin)
diff --git a/production/apps.py b/production/apps.py
index d633dcd69d8e1462ea12d64fa04cf8492c2dafe9..c7df2ea8326bea092da1a6f5ce4562710650dafd 100644
--- a/production/apps.py
+++ b/production/apps.py
@@ -8,7 +8,8 @@ class ProductionConfig(AppConfig):
     def ready(self):
         super().ready()
 
-        from .models import ProductionEvent, ProductionStream
-        from .signals import notify_new_event, notify_new_stream
+        from .models import ProductionEvent, ProductionStream, Proofs
+        from .signals import notify_new_event, notify_new_stream, notify_proofs_upload
         post_save.connect(notify_new_event, sender=ProductionEvent)
         post_save.connect(notify_new_stream, sender=ProductionStream)
+        post_save.connect(notify_proofs_upload, sender=Proofs)
diff --git a/production/constants.py b/production/constants.py
index e9453a6f7d140b0902509621cd54f3d616d3a132..8c7f309dc63976aa7c03dca48b7b41fc61380a3f 100644
--- a/production/constants.py
+++ b/production/constants.py
@@ -1,23 +1,57 @@
-PRODUCTION_STREAM_ONGOING = 'ongoing'
+PRODUCTION_STREAM_INITIATED = 'initiated'
 PRODUCTION_STREAM_COMPLETED = 'completed'
+PROOFS_TASKED = 'tasked'
+PROOFS_PRODUCED = 'produced'
+PROOFS_CHECKED = 'checked'
+PROOFS_SENT = 'sent'
+PROOFS_RETURNED = 'returned'
+PROOFS_CORRECTED = 'corrected'
+PROOFS_ACCEPTED = 'accepted'
+PROOFS_PUBLISHED = 'published'
+PROOFS_CITED = 'cited'
 PRODUCTION_STREAM_STATUS = (
-    (PRODUCTION_STREAM_ONGOING, 'Ongoing'),
+    (PRODUCTION_STREAM_INITIATED, 'New Stream started'),
+    (PROOFS_TASKED, 'Supervisor tasked officer with proofs production'),
+    (PROOFS_PRODUCED, 'Proofs have been produced'),
+    (PROOFS_CHECKED, 'Proofs have been checked by Supervisor'),
+    (PROOFS_SENT, 'Proofs sent to Authors'),
+    (PROOFS_RETURNED, 'Proofs returned by Authors'),
+    (PROOFS_CORRECTED, 'Corrections implemented'),
+    (PROOFS_ACCEPTED, 'Authors have accepted proofs'),
+    (PROOFS_PUBLISHED, 'Paper has been published'),
+    (PROOFS_CITED, 'Cited people have been notified/invited to SciPost'),
     (PRODUCTION_STREAM_COMPLETED, 'Completed'),
 )
 
+EVENT_MESSAGE = 'message'
+EVENT_HOUR_REGISTRATION = 'registration'
 PRODUCTION_EVENTS = (
-    ('assigned_to_supervisor', 'Assigned by EdAdmin to Supervisor'),
-    ('message_edadmin_to_supervisor', 'Message from EdAdmin to Supervisor'),
-    ('message_supervisor_to_edadmin', 'Message from Supervisor to EdAdmin'),
-    ('officer_tasked_with_proof_production', 'Supervisor tasked officer with proofs production'),
-    ('message_supervisor_to_officer', 'Message from Supervisor to Officer'),
-    ('message_officer_to_supervisor', 'Message from Officer to Supervisor'),
-    ('proofs_produced', 'Proofs have been produced'),
-    ('proofs_checked_by_supervisor', 'Proofs have been checked by Supervisor'),
-    ('proofs_sent_to_authors', 'Proofs sent to Authors'),
-    ('proofs_returned_by_authors', 'Proofs returned by Authors'),
-    ('corrections_implemented', 'Corrections implemented'),
-    ('authors_have_accepted_proofs', 'Authors have accepted proofs'),
-    ('paper_published', 'Paper has been published'),
-    ('cited_notified', 'Cited people have been notified/invited to SciPost'),
+    ('assignment', 'Assignment'),
+    ('status', 'Status change'),
+    (EVENT_MESSAGE, 'Message'),
+    (EVENT_HOUR_REGISTRATION, 'Hour registration'),
+)
+
+PROOFS_UPLOADED = 'uploaded'
+PROOFS_SENT = 'sent'
+PROOFS_ACCEPTED_SUP = 'accepted_sup'
+PROOFS_DECLINED_SUP = 'declined_sup'
+PROOFS_DECLINED = 'declined'
+PROOFS_RENEWED = 'renewed'
+PROOFS_STATUSES = (
+    (PROOFS_UPLOADED, 'Proofs uploaded'),
+    (PROOFS_SENT, 'Proofs sent to authors'),
+    (PROOFS_ACCEPTED_SUP, 'Proofs accepted by supervisor'),
+    (PROOFS_DECLINED_SUP, 'Proofs declined by supervisor'),
+    (PROOFS_ACCEPTED, 'Proofs accepted by authors'),
+    (PROOFS_DECLINED, 'Proofs declined by authors'),
+    (PROOFS_RENEWED, 'Proofs renewed'),
+)
+
+PRODUCTION_OFFICERS_WORK_LOG_TYPES = (
+    ('Production: Production Officer tasks', 'Production Officer tasks'),
+)
+PRODUCTION_ALL_WORK_LOG_TYPES = (
+    ('Production: Supervisory tasks', 'Supervisory tasks'),
+    ('Production: Production Officer tasks', 'Production Officer tasks'),
 )
diff --git a/production/forms.py b/production/forms.py
index 4c284c839e6bd44202ee6d78d5479515211f1c2a..1768d6d12d07e8e59cf64f72db7e501dea1d64ae 100644
--- a/production/forms.py
+++ b/production/forms.py
@@ -1,11 +1,10 @@
 import datetime
 
 from django import forms
-from django.contrib.auth import get_user_model
-from django.utils.dates import MONTHS
-from django.db.models import Sum
 
-from .models import ProductionUser, ProductionStream, ProductionEvent
+from . import constants
+from .models import ProductionUser, ProductionStream, ProductionEvent, Proofs
+from .signals import notify_stream_status_change
 
 today = datetime.datetime.today()
 
@@ -14,13 +13,10 @@ class ProductionEventForm(forms.ModelForm):
     class Meta:
         model = ProductionEvent
         fields = (
-            'event',
             'comments',
-            'duration'
         )
         widgets = {
             'comments': forms.Textarea(attrs={'rows': 4}),
-            'duration': forms.TextInput(attrs={'placeholder': 'HH:MM:SS'})
         }
 
 
@@ -29,6 +25,20 @@ class AssignOfficerForm(forms.ModelForm):
         model = ProductionStream
         fields = ('officer',)
 
+    def save(self, commit=True):
+        stream = super().save(False)
+        if commit:
+            if stream.status == constants.PRODUCTION_STREAM_INITIATED:
+                stream.status = constants.PROOFS_TASKED
+            stream.save()
+        return stream
+
+
+class AssignInvitationsOfficerForm(forms.ModelForm):
+    class Meta:
+        model = ProductionStream
+        fields = ('invitations_officer',)
+
 
 class AssignSupervisorForm(forms.ModelForm):
     class Meta:
@@ -41,6 +51,76 @@ class AssignSupervisorForm(forms.ModelForm):
             user__groups__name='Production Supervisor')
 
 
+class StreamStatusForm(forms.ModelForm):
+    class Meta:
+        model = ProductionStream
+        fields = ('status',)
+
+    def __init__(self, *args, **kwargs):
+        self.current_production_user = kwargs.pop('production_user')
+        super().__init__(*args, **kwargs)
+        self.fields['status'].choices = self.get_available_statuses()
+
+    def get_available_statuses(self):
+        if self.instance.status in [constants.PRODUCTION_STREAM_INITIATED,
+                                    constants.PRODUCTION_STREAM_COMPLETED,
+                                    constants.PROOFS_ACCEPTED,
+                                    constants.PROOFS_CITED]:
+            # No status change can be made by User
+            return ()
+        elif self.instance.status == constants.PROOFS_TASKED:
+            return (
+                (constants.PROOFS_PRODUCED, 'Proofs have been produced'),
+            )
+        elif self.instance.status == constants.PROOFS_PRODUCED:
+            return (
+                (constants.PROOFS_CHECKED, 'Proofs have been checked by Supervisor'),
+                (constants.PROOFS_SENT, 'Proofs sent to Authors'),
+            )
+        elif self.instance.status == constants.PROOFS_CHECKED:
+            return (
+                (constants.PROOFS_SENT, 'Proofs sent to Authors'),
+                (constants.PROOFS_CORRECTED, 'Corrections implemented'),
+            )
+        elif self.instance.status == constants.PROOFS_SENT:
+            return (
+                (constants.PROOFS_RETURNED, 'Proofs returned by Authors'),
+                (constants.PROOFS_ACCEPTED, 'Authors have accepted proofs'),
+            )
+        elif self.instance.status == constants.PROOFS_RETURNED:
+            return (
+                (constants.PROOFS_CHECKED, 'Proofs have been checked by Supervisor'),
+                (constants.PROOFS_SENT, 'Proofs sent to Authors'),
+                (constants.PROOFS_CORRECTED, 'Corrections implemented'),
+                (constants.PROOFS_ACCEPTED, 'Authors have accepted proofs'),
+            )
+        elif self.instance.status == constants.PROOFS_CORRECTED:
+            return (
+                (constants.PROOFS_CHECKED, 'Proofs have been checked by Supervisor'),
+                (constants.PROOFS_SENT, 'Proofs sent to Authors'),
+                (constants.PROOFS_ACCEPTED, 'Authors have accepted proofs'),
+            )
+        elif self.instance.status == constants.PROOFS_PUBLISHED:
+            return (
+                (constants.PROOFS_CITED, 'Cited people have been notified/invited to SciPost'),
+            )
+        return ()
+
+    def save(self, commit=True):
+        stream = super().save(commit)
+        if commit:
+            event = ProductionEvent(
+                stream=stream,
+                event='status',
+                comments='Stream changed status to: {status}'.format(
+                    status=stream.get_status_display()),
+                noted_by=self.current_production_user)
+            event.save()
+            notify_stream_status_change(sender=self.current_production_user.user, instance=stream,
+                                        created=False)
+        return stream
+
+
 class UserToOfficerForm(forms.ModelForm):
     class Meta:
         model = ProductionUser
@@ -54,41 +134,48 @@ class UserToOfficerForm(forms.ModelForm):
             production_user__isnull=True).order_by('last_name')
 
 
-class ProductionUserMonthlyActiveFilter(forms.Form):
-    month = forms.ChoiceField(choices=[(k, v) for k, v in MONTHS.items()])
-    year = forms.ChoiceField(choices=[(y, y) for y in reversed(range(today.year-6, today.year+1))])
+class ProofsUploadForm(forms.ModelForm):
+    class Meta:
+        model = Proofs
+        fields = ('attachment',)
 
-    def __init__(self, *args, **kwargs):
-        if not kwargs.get('data', False) and not args[0]:
-            args = list(args)
-            args[0] = {
-                'month': today.month,
-                'year': today.year
-            }
-            args = tuple(args)
-        kwargs['initial'] = {
-            'month': today.month,
-            'year': today.year
-        }
-        super().__init__(*args, **kwargs)
 
-    def get_totals(self):
-        # Make accessible without need to explicitly check validity of form.
-        self.is_valid()
-
-        users = ProductionUser.objects.filter(events__duration__isnull=False,
-                                              events__noted_on__month=self.cleaned_data['month'],
-                                              events__noted_on__year=self.cleaned_data['year']
-                                              ).distinct()
-        output = []
-        for user in users:
-            events = user.events.filter(duration__isnull=False,
-                                        noted_on__month=self.cleaned_data['month'],
-                                        noted_on__year=self.cleaned_data['year'])
-            output.append({
-                'events': events,
-                'duration': events.aggregate(total=Sum('duration')),
-                'user': user
-            })
-
-        return output
+class ProofsDecisionForm(forms.ModelForm):
+    decision = forms.ChoiceField(choices=[(True, 'Accept Proofs for publication'),
+                                          (False, 'Decline Proofs for publication')])
+    feedback = forms.CharField(required=False, widget=forms.Textarea)
+
+    class Meta:
+        model = Proofs
+        fields = ()
+
+    def save(self, commit=True):
+        proofs = self.instance
+        decision = self.cleaned_data['decision']
+        comments = self.cleaned_data['feedback']
+
+        if decision in ['True', True]:
+            proofs.status = constants.PROOFS_ACCEPTED
+            if proofs.stream.status in [constants.PROOFS_PRODUCED,
+                                        constants.PROOFS_CHECKED,
+                                        constants.PROOFS_SENT,
+                                        constants.PROOFS_CORRECTED]:
+                # Force status change on Stream if appropriate
+                proofs.stream.status = constants.PROOFS_ACCEPTED
+        else:
+            proofs.status = constants.PROOFS_DECLINED
+            proofs.stream.status = constants.PROOFS_RETURNED
+
+        if commit:
+            proofs.save()
+            proofs.stream.save()
+
+            prodevent = ProductionEvent(
+                stream=proofs.stream,
+                event='status',
+                comments='Received feedback from the authors: {comments}'.format(
+                    comments=comments),
+                noted_by=proofs.stream.supervisor
+            )
+            prodevent.save()
+        return proofs
diff --git a/production/managers.py b/production/managers.py
index 1980044f4840ed438519832433c5603dbe9fa012..5bd1b5c00b0adc2df29de3e8b7c59881a4235fec 100644
--- a/production/managers.py
+++ b/production/managers.py
@@ -1,19 +1,33 @@
 from django.db import models
 
-from .constants import PRODUCTION_STREAM_COMPLETED, PRODUCTION_STREAM_ONGOING
+from . import constants
 
 
 class ProductionStreamQuerySet(models.QuerySet):
     def completed(self):
-        return self.filter(status=PRODUCTION_STREAM_COMPLETED)
+        return self.filter(status=constants.PRODUCTION_STREAM_COMPLETED)
 
     def ongoing(self):
-        return self.filter(status=PRODUCTION_STREAM_ONGOING)
+        return self.exclude(status=constants.PRODUCTION_STREAM_COMPLETED)
 
     def filter_for_user(self, production_user):
-        return self.filter(officer=production_user)
+        """
+        Return ProductionStreams that are only assigned to me as a Production Officer
+        or a Inivtations Officer.
+        """
+        return self.filter(models.Q(officer=production_user)
+                           | models.Q(invitations_officer=production_user))
 
 
 class ProductionEventManager(models.Manager):
     def get_my_events(self, production_user):
         return self.filter(noted_by=production_user)
+
+
+class ProofsQuerySet(models.QuerySet):
+    def for_authors(self):
+        return self.filter(accessible_for_authors=True)
+
+    def can_be_send(self):
+        return self.filter(status__in=[constants.PROOFS_UPLOADED, constants.PROOFS_SENT,
+                                       constants.PROOFS_ACCEPTED_SUP])
diff --git a/production/migrations/0021_auto_20170930_1729.py b/production/migrations/0021_auto_20170930_1729.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ce6938c8f41af77778bb02e1dc1484e69eb73a1
--- /dev/null
+++ b/production/migrations/0021_auto_20170930_1729.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-30 15:29
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0020_auto_20170930_0156'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='productionevent',
+            name='noted_to',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='received_events', to='production.ProductionUser'),
+        ),
+        migrations.AlterField(
+            model_name='productionevent',
+            name='event',
+            field=models.CharField(choices=[('assignment', 'Assignment'), ('message_edadmin_to_supervisor', 'Message from EdAdmin to Supervisor'), ('message_supervisor_to_edadmin', 'Message from Supervisor to EdAdmin'), ('officer_tasked_with_proof_production', 'Supervisor tasked officer with proofs production'), ('message_supervisor_to_officer', 'Message from Supervisor to Officer'), ('message_officer_to_supervisor', 'Message from Officer to Supervisor'), ('proofs_produced', 'Proofs have been produced'), ('proofs_checked_by_supervisor', 'Proofs have been checked by Supervisor'), ('proofs_sent_to_authors', 'Proofs sent to Authors'), ('proofs_returned_by_authors', 'Proofs returned by Authors'), ('corrections_implemented', 'Corrections implemented'), ('authors_have_accepted_proofs', 'Authors have accepted proofs'), ('paper_published', 'Paper has been published'), ('cited_notified', 'Cited people have been notified/invited to SciPost')], max_length=64),
+        ),
+    ]
diff --git a/production/migrations/0022_auto_20170930_1756.py b/production/migrations/0022_auto_20170930_1756.py
new file mode 100644
index 0000000000000000000000000000000000000000..4fa6bca1455899768adacb80e6f83fc36533fcc2
--- /dev/null
+++ b/production/migrations/0022_auto_20170930_1756.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-30 15:56
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+def update_status(apps, schema_editor):
+    """
+    Update current Production Stream status by checking their events content.
+    """
+    ProductionStream = apps.get_model('production', 'ProductionStream')
+    for stream in ProductionStream.objects.all():
+        if stream.status == 'completed':
+            stream.status = 'completed'
+        elif stream.events.filter(event='cited_notified').exists():
+            stream.status = 'cited'
+        elif stream.events.filter(event='paper_published').exists():
+            stream.status = 'published'
+        elif stream.events.filter(event='authors_have_accepted_proofs').exists():
+            stream.status = 'accepted'
+        elif stream.events.filter(event='corrections_implemented').exists():
+            stream.status = 'corrected'
+        elif stream.events.filter(event='proofs_returned_by_authors').exists():
+            stream.status = 'returned'
+        elif stream.events.filter(event='proofs_sent_to_authors').exists():
+            stream.status = 'sent'
+        elif stream.events.filter(event='proofs_checked_by_supervisor').exists():
+            stream.status = 'checked'
+        elif stream.events.filter(event='officer_tasked_with_proof_production').exists():
+            stream.status = 'tasked'
+        else:
+            stream.status = 'initiated'
+        stream.save()
+    return
+
+
+def update_status_inverse(apps, schema_editor):
+    """
+    Reverse update statuses to only complete/ongoing by checking the status.
+    """
+    ProductionStream = apps.get_model('production', 'ProductionStream')
+    for stream in ProductionStream.objects.all():
+        if stream.status == 'completed':
+            stream.status = 'completed'
+        else:
+            stream.status == 'ongoing'
+        stream.save()
+    return
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0021_auto_20170930_1729'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='productionstream',
+            name='status',
+            field=models.CharField(choices=[('initiated', 'Initiated'), ('tasked', 'Supervisor tasked officer with proofs production'), ('produced', 'Proofs have been produced'), ('checked', 'Proofs have been checked by Supervisor'), ('sent', 'Proofs sent to Authors'), ('returned', 'Proofs returned by Authors'), ('corrected', 'Corrections implemented'), ('accepted', 'Authors have accepted proofs'), ('published', 'Paper has been published'), ('cited', 'Cited people have been notified/invited to SciPost'), ('completed', 'Completed')], default='initiated', max_length=32),
+        ),
+        migrations.RunPython(update_status, update_status_inverse)
+    ]
diff --git a/production/migrations/0023_auto_20170930_1759.py b/production/migrations/0023_auto_20170930_1759.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f510418996feeeaa5376a1e1af99baba670296c
--- /dev/null
+++ b/production/migrations/0023_auto_20170930_1759.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-30 15:59
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0022_auto_20170930_1756'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='productionevent',
+            name='event',
+            field=models.CharField(choices=[('assignment', 'Assignment'), ('status', 'Status change'), ('message', 'Message'), ('registration', 'Registration hours')], max_length=64),
+        ),
+    ]
diff --git a/production/migrations/0024_auto_20170930_2230.py b/production/migrations/0024_auto_20170930_2230.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb444dd499d84b11bc866bd2004638c83a07ae43
--- /dev/null
+++ b/production/migrations/0024_auto_20170930_2230.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-30 20:30
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import production.models
+import scipost.storage
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('production', '0023_auto_20170930_1759'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Proof',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('attachment', models.FileField(storage=scipost.storage.SecureFileStorage(), upload_to=production.models.proofs_upload_location)),
+                ('version', models.PositiveSmallIntegerField(default=0)),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('status', models.CharField(choices=[('uploaded', 'Proof uploaded'), ('accepted', 'Proof accepted'), ('declined', 'Proof declined'), ('renewed', 'Proof renewed')], default='uploaded', max_length=16)),
+            ],
+        ),
+        migrations.AlterField(
+            model_name='productionevent',
+            name='event',
+            field=models.CharField(choices=[('assignment', 'Assignment'), ('status', 'Status change'), ('message', 'Message'), ('registration', 'Hour registration')], default='message', max_length=64),
+        ),
+        migrations.AlterField(
+            model_name='productionstream',
+            name='status',
+            field=models.CharField(choices=[('initiated', 'New Stream started'), ('tasked', 'Supervisor tasked officer with proofs production'), ('produced', 'Proofs have been produced'), ('checked', 'Proofs have been checked by Supervisor'), ('sent', 'Proofs sent to Authors'), ('returned', 'Proofs returned by Authors'), ('corrected', 'Corrections implemented'), ('accepted', 'Authors have accepted proofs'), ('published', 'Paper has been published'), ('cited', 'Cited people have been notified/invited to SciPost'), ('completed', 'Completed')], default='initiated', max_length=32),
+        ),
+        migrations.AlterField(
+            model_name='productionstream',
+            name='submission',
+            field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='production_stream', to='submissions.Submission'),
+        ),
+        migrations.AddField(
+            model_name='proof',
+            name='stream',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='proofs', to='production.ProductionStream'),
+        ),
+        migrations.AddField(
+            model_name='proof',
+            name='upload_by',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/production/migrations/0025_auto_20170930_2244.py b/production/migrations/0025_auto_20170930_2244.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8b1ef0629676a83aff2a9b9709e0ac47446b9e6
--- /dev/null
+++ b/production/migrations/0025_auto_20170930_2244.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-30 20:44
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0024_auto_20170930_2230'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='proof',
+            name='upload_by',
+        ),
+        migrations.AddField(
+            model_name='proof',
+            name='uploaded_by',
+            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='production.ProductionUser'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/production/migrations/0026_proof_accessible_for_authors.py b/production/migrations/0026_proof_accessible_for_authors.py
new file mode 100644
index 0000000000000000000000000000000000000000..4854c5c206ad4cc4205abb2fa3e7f1ebf6fba6e3
--- /dev/null
+++ b/production/migrations/0026_proof_accessible_for_authors.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-30 20:49
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0025_auto_20170930_2244'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='proof',
+            name='accessible_for_authors',
+            field=models.BooleanField(default=False),
+        ),
+    ]
diff --git a/production/migrations/0027_merge_20171004_1947.py b/production/migrations/0027_merge_20171004_1947.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9eba172cf3fe8a97f2ef8e0bc137497a70a4ddf
--- /dev/null
+++ b/production/migrations/0027_merge_20171004_1947.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-04 17:47
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0022_auto_20171003_1525'),
+        ('production', '0026_proof_accessible_for_authors'),
+    ]
+
+    operations = [
+    ]
diff --git a/production/migrations/0028_auto_20171007_1311.py b/production/migrations/0028_auto_20171007_1311.py
new file mode 100644
index 0000000000000000000000000000000000000000..547f5cb709e966342cc30782477dbd275f98471c
--- /dev/null
+++ b/production/migrations/0028_auto_20171007_1311.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-07 11:11
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0027_merge_20171004_1947'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='proof',
+            options={'ordering': ['stream', 'version']},
+        ),
+        migrations.AlterField(
+            model_name='proof',
+            name='status',
+            field=models.CharField(choices=[('uploaded', 'Proofs uploaded'), ('sent', 'Proofs sent to authors'), ('accepted_sup', 'Proofs accepted by supervisor'), ('declined_sup', 'Proofs declined by supervisor'), ('accepted', 'Proofs accepted by authors'), ('declined', 'Proofs declined by authors'), ('renewed', 'Proofs renewed')], default='uploaded', max_length=16),
+        ),
+    ]
diff --git a/production/migrations/0029_productionstream_invitations_officer.py b/production/migrations/0029_productionstream_invitations_officer.py
new file mode 100644
index 0000000000000000000000000000000000000000..8159df2f15ab42cbdc42de5ac6687348ed85b2d7
--- /dev/null
+++ b/production/migrations/0029_productionstream_invitations_officer.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-09 18:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0028_auto_20171007_1311'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='productionstream',
+            name='invitations_officer',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='invitations_officer_streams', to='production.ProductionUser'),
+        ),
+    ]
diff --git a/production/migrations/0030_auto_20171009_2111.py b/production/migrations/0030_auto_20171009_2111.py
new file mode 100644
index 0000000000000000000000000000000000000000..928cc21970a30445f1f84f3a7ab46b9c3e8342df
--- /dev/null
+++ b/production/migrations/0030_auto_20171009_2111.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-09 19:11
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0029_productionstream_invitations_officer'),
+    ]
+
+    operations = [
+        migrations.RenameModel(
+            old_name='Proof',
+            new_name='Proofs',
+        ),
+    ]
diff --git a/production/migrations/0031_auto_20171010_0921.py b/production/migrations/0031_auto_20171010_0921.py
new file mode 100644
index 0000000000000000000000000000000000000000..dcf9fe6f6dad9b30fe69346ce5747543fc0a247d
--- /dev/null
+++ b/production/migrations/0031_auto_20171010_0921.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-10 07:21
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0030_auto_20171009_2111'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='proofs',
+            options={'ordering': ['stream', 'version'], 'verbose_name_plural': 'Proofs'},
+        ),
+    ]
diff --git a/production/migrations/0032_auto_20171010_1008.py b/production/migrations/0032_auto_20171010_1008.py
new file mode 100644
index 0000000000000000000000000000000000000000..e92a693b6487c1f75028d2d3c3ace21afef92dd8
--- /dev/null
+++ b/production/migrations/0032_auto_20171010_1008.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-10 08:08
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+def update_status(apps, schema_editor):
+    """
+    Update current Production Event type.
+    """
+    ProductionEvent = apps.get_model('production', 'ProductionEvent')
+    for event in ProductionEvent.objects.all():
+        if event.duration:
+            event.event = 'registration'
+        elif event.event == ['assigned_to_supervisor', 'officer_tasked_with_proof_production']:
+            event.event = 'assignment'
+        elif event.event in ['message_edadmin_to_supervisor', 'message_supervisor_to_edadmin', 'message_supervisor_to_officer', 'message_officer_to_supervisor']:
+            event.event = 'message'
+        else:
+            event.event = 'status'
+        event.save()
+    return
+
+
+def update_status_inverse(apps, schema_editor):
+    """
+    Inverse update current Production Event type. As this mapping is impossible to make,
+    it'll just all be a unique status: `Event`
+    """
+    ProductionEvent = apps.get_model('production', 'ProductionEvent')
+    for event in ProductionEvent.objects.all():
+        event.event = 'Event'
+    return
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('finances', '0006_auto_20171010_1003'),
+        ('production', '0031_auto_20171010_0921'),
+    ]
+
+    operations = [
+        # Do not run this migration,
+        #  the field is U/S anyway and in case it goes wrong we loose data here.
+
+        # migrations.RunPython(update_status, update_status_inverse)
+    ]
diff --git a/production/models.py b/production/models.py
index 17f2d73c427e11c0ebfa04d2f3d77a7fca235400..549027075dea4866f62f1cb3cc2d5620cfd60868 100644
--- a/production/models.py
+++ b/production/models.py
@@ -1,10 +1,18 @@
 from django.db import models
+from django.contrib.contenttypes.fields import GenericRelation
 from django.core.urlresolvers import reverse
 from django.contrib.auth.models import User
 from django.utils import timezone
+from django.utils.functional import cached_property
 
-from .constants import PRODUCTION_STREAM_STATUS, PRODUCTION_STREAM_ONGOING, PRODUCTION_EVENTS
-from .managers import ProductionStreamQuerySet, ProductionEventManager
+from .constants import PRODUCTION_STREAM_STATUS, PRODUCTION_STREAM_INITIATED, PRODUCTION_EVENTS,\
+                       EVENT_MESSAGE, EVENT_HOUR_REGISTRATION, PRODUCTION_STREAM_COMPLETED,\
+                       PROOFS_STATUSES, PROOFS_UPLOADED
+from .managers import ProductionStreamQuerySet, ProductionEventManager, ProofsQuerySet
+from .utils import proofs_id_to_slug
+
+from finances.models import WorkLog
+from scipost.storage import SecureFileStorage
 
 
 class ProductionUser(models.Model):
@@ -22,15 +30,21 @@ class ProductionUser(models.Model):
 
 
 class ProductionStream(models.Model):
-    submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE)
+    submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE,
+                                      related_name='production_stream')
     opened = models.DateTimeField(auto_now_add=True)
     closed = models.DateTimeField(default=timezone.now)
-    status = models.CharField(max_length=32,
-                              choices=PRODUCTION_STREAM_STATUS, default=PRODUCTION_STREAM_ONGOING)
+    status = models.CharField(max_length=32, choices=PRODUCTION_STREAM_STATUS,
+                              default=PRODUCTION_STREAM_INITIATED)
+
     officer = models.ForeignKey('production.ProductionUser', blank=True, null=True,
                                 related_name='streams')
     supervisor = models.ForeignKey('production.ProductionUser', blank=True, null=True,
                                    related_name='supervised_streams')
+    invitations_officer = models.ForeignKey('production.ProductionUser', blank=True, null=True,
+                                            related_name='invitations_officer_streams')
+
+    work_logs = GenericRelation(WorkLog, related_query_name='streams')
 
     objects = ProductionStreamQuerySet.as_manager()
 
@@ -45,22 +59,27 @@ class ProductionStream(models.Model):
                                          title=self.submission.title)
 
     def get_absolute_url(self):
-        if self.status == PRODUCTION_STREAM_ONGOING:
-            return reverse('production:production') + '#stream_' + str(self.id)
-        return reverse('production:completed') + '#stream_' + str(self.id)
+        return reverse('production:stream', args=(self.id,))
 
+    @cached_property
     def total_duration(self):
-        totdur = self.events.aggregate(models.Sum('duration'))
+        totdur = self.work_logs.aggregate(models.Sum('duration'))
         return totdur['duration__sum']
 
+    @cached_property
+    def completed(self):
+        return self.status == PRODUCTION_STREAM_COMPLETED
+
 
 class ProductionEvent(models.Model):
     stream = models.ForeignKey(ProductionStream, on_delete=models.CASCADE, related_name='events')
-    event = models.CharField(max_length=64, choices=PRODUCTION_EVENTS)
+    event = models.CharField(max_length=64, choices=PRODUCTION_EVENTS, default=EVENT_MESSAGE)
     comments = models.TextField(blank=True, null=True)
     noted_on = models.DateTimeField(default=timezone.now)
     noted_by = models.ForeignKey('production.ProductionUser', on_delete=models.CASCADE,
                                  related_name='events')
+    noted_to = models.ForeignKey('production.ProductionUser', on_delete=models.CASCADE,
+                                 blank=True, null=True, related_name='received_events')
     duration = models.DurationField(blank=True, null=True)
 
     objects = ProductionEventManager()
@@ -73,3 +92,51 @@ class ProductionEvent(models.Model):
 
     def get_absolute_url(self):
         return self.stream.get_absolute_url()
+
+    @cached_property
+    def editable(self):
+        return self.event in [EVENT_MESSAGE, EVENT_HOUR_REGISTRATION] and not self.stream.completed
+
+
+def proofs_upload_location(instance, filename):
+    submission = instance.stream.submission
+    return 'UPLOADS/PROOFS/{year}/{arxiv}/{filename}'.format(
+        year=submission.submission_date.year,
+        arxiv=submission.arxiv_identifier_wo_vn_nr,
+        filename=filename)
+
+
+class Proofs(models.Model):
+    """
+    Proofs are directly related to a ProductionStream and Submission in SciPost.
+    """
+    attachment = models.FileField(upload_to=proofs_upload_location, storage=SecureFileStorage())
+    version = models.PositiveSmallIntegerField(default=0)
+    stream = models.ForeignKey('production.ProductionStream', related_name='proofs')
+    uploaded_by = models.ForeignKey('production.ProductionUser', related_name='+')
+    created = models.DateTimeField(auto_now_add=True)
+    status = models.CharField(max_length=16, choices=PROOFS_STATUSES, default=PROOFS_UPLOADED)
+    accessible_for_authors = models.BooleanField(default=False)
+
+    objects = ProofsQuerySet.as_manager()
+
+    class Meta:
+        ordering = ['stream', 'version']
+        verbose_name_plural = 'Proofs'
+
+    def get_absolute_url(self):
+        return reverse('production:proofs_pdf', kwargs={'slug': self.slug})
+
+    def __str__(self):
+        return 'Proofs {version} for Stream {stream}'.format(
+            version=self.version, stream=self.stream.submission.title)
+
+    def save(self, *args, **kwargs):
+        # Control Report count per Submission.
+        if not self.version:
+            self.version = self.stream.proofs.count() + 1
+        return super().save(*args, **kwargs)
+
+    @property
+    def slug(self):
+        return proofs_id_to_slug(self.id)
diff --git a/production/signals.py b/production/signals.py
index 31327d531302210210e7c4d53557592a7305a655..c712528527cec7aace9a12f535cbac956a7e2f6e 100644
--- a/production/signals.py
+++ b/production/signals.py
@@ -1,5 +1,7 @@
 from django.contrib.auth.models import Group
 
+from .import constants
+
 from notifications.signals import notify
 
 
@@ -9,8 +11,8 @@ def notify_new_stream(sender, instance, created, **kwargs):
     """
     if created:
         editorial_college = Group.objects.get(name='Editorial College')
-        supervisors = Group.objects.get(name='Production Supervisor')
-        for recipient in supervisors.user_set.all():
+        administators = Group.objects.get(name='Editorial Administrators')
+        for recipient in administators.user_set.all():
             notify.send(sender=sender, recipient=recipient, actor=editorial_college,
                         verb=' accepted a Submission. A new Production Stream has started.',
                         target=instance)
@@ -31,23 +33,60 @@ def notify_new_event(sender, instance, created, **kwargs):
     if created:
         stream = instance.stream
 
-        if stream.officer != instance.noted_by:
+        if stream.officer and stream.officer != instance.noted_by:
             notify.send(sender=sender, recipient=stream.officer.user,
                         actor=instance.noted_by.user,
                         verb=' created a new Production Event.', target=instance)
 
-        if stream.supervisor != instance.noted_by:
+        if stream.supervisor and stream.supervisor != instance.noted_by:
             notify.send(sender=sender, recipient=stream.supervisor.user,
                         actor=instance.noted_by.user,
                         verb=' created a new Production Event.', target=instance)
 
 
-def notify_stream_completed(sender, instance, **kwargs):
+def notify_stream_status_change(sender, instance, created, **kwargs):
     """
-    Notify the production team about a Production Stream being completed.
+    Notify the production officers about a new status change for a Production Stream.
+
+    sender -- User instance
+    instance -- ProductionStream instance
     """
-    notify.send(sender=sender, recipient=instance.officer.user,
-                actor=sender, verb=' marked Production Stream as completed.', target=instance)
+    if instance.status == constants.PROOFS_ACCEPTED:
+        administators = Group.objects.get(name='Editorial Administrators')
+        for user in administators.user_set.all():
+            notify.send(sender=sender, recipient=user,
+                        actor=sender,
+                        verb=' has marked Proofs accepted.', target=instance)
+    elif instance.status == constants.PROOFS_PUBLISHED:
+        if instance.supervisor:
+            notify.send(sender=sender, recipient=instance.supervisor.user,
+                        actor=sender,
+                        verb=' published the manuscript of your Production Stream.',
+                        target=instance)
+
+    elif instance.status == constants.PRODUCTION_STREAM_COMPLETED:
+        if instance.supervisor:
+            notify.send(sender=sender, recipient=instance.supervisor.user,
+                        actor=sender,
+                        verb=' marked your Production Stream as completed.', target=instance)
+        if instance.officer:
+            notify.send(sender=sender, recipient=instance.officer.user,
+                        actor=sender,
+                        verb=' marked your Production Stream as completed.', target=instance)
+    else:
+        if instance.officer:
+            notify.send(sender=sender, recipient=instance.officer.user,
+                        actor=sender,
+                        verb=' changed the Production Stream status.', target=instance)
+
+        if instance.supervisor:
+            notify.send(sender=sender, recipient=instance.supervisor.user,
+                        actor=sender,
+                        verb=' changed the Production Stream status.', target=instance)
+
 
-    notify.send(sender=sender, recipient=instance.supervisor.user,
-                actor=sender, verb=' marked Production Stream as completed.', target=instance)
+def notify_proofs_upload(sender, instance, created, **kwargs):
+    if created and instance.stream.supervisor:
+        notify.send(sender=sender, recipient=instance.stream.supervisor.user,
+                    actor=instance.uploaded_by.user, verb=' uploaded new Proofs to Production Stream.',
+                    target=instance)
diff --git a/production/templates/production/partials/production_events.html b/production/templates/production/partials/production_events.html
index 4db2ab632d6bd8355d34b79b84577c94588651dc..653f9c3df4bbf1999172d6fd62d320206a720b41 100644
--- a/production/templates/production/partials/production_events.html
+++ b/production/templates/production/partials/production_events.html
@@ -1,30 +1,45 @@
 {% load scipost_extras %}
 
-<ul>
+<ul class="list-unstyled">
   {% for event in events %}
-        <li id="event_{{ event.id }}">
-          <p class="mb-0 font-weight-bold">{{ event.get_event_display }}
-            {% if not non_editable %}
-              {% if event.noted_by == request.user.production_user %}
-                  &middot; <a href="{% url 'production:update_event' event.id %}">Edit</a>
-                  &middot; <a class="text-danger" href="{% url 'production:delete_event' event.id %}">Delete</a>
-              {% endif %}
-            {% endif %}
-          </p>
-          <p class="text-muted mb-1">noted {{ event.noted_on }} by {{ event.noted_by }}</p>
-          {% if event.duration %}
-              <div class="mb-2">Duration: {{ event.duration|duration }}</div>
-          {% endif %}
+        <li id="event_{{ event.id }}" class="pb-2">
+            <div class="d-flex justify-content-between">
+                <div>
+                    <strong>{{ event.noted_by.user.first_name }} {{ event.noted_by.user.last_name }}</strong>
+                    <br>
+                    {{ event.get_event_displa|linebreaksbr }}
+                </div>
+                <div class="text-muted text-right d-flex justify-content-end">
+                    <div>
+                        {{ event.noted_on }}
+                        {% if event.duration %}
+                            <br>
+                            <strong>Duration: {{ event.duration|duration }}</strong>
+                        {% endif %}
+                    </div>
+
+                    {% if not non_editable %}
+                      {% if event.noted_by == request.user.production_user and event.editable %}
+                          <div class="pl-2">
+                              <a href="{% url 'production:update_event' event.id %}"><i class="fa fa-pencil-square-o" aria-hidden="true"></i></a>
+                              <a class="text-danger" href="{% url 'production:delete_event' event.id %}"><i class="fa fa-trash" aria-hidden="true"></i></a>
+                          </div>
+                      {% endif %}
+                    {% endif %}
+                </div>
+            </div>
+
           {% if event.comments %}
-              <div>{{ event.comments|linebreaks }}</div>
+              <p class="mt-2 mb-0">
+                  {% if event.noted_to %}
+                    {{ event.noted_by.user.first_name }} {{ event.noted_by.user.last_name }} {{ event.comments|linebreaksbr }} {{ event.noted_to.user.first_name }} {{ event.noted_to.user.last_name }}.
+                  {% else %}
+                      {{ event.comments|linebreaksbr }}
+                  {% endif %}
+              </p>
           {% endif %}
         </li>
     {% empty %}
         <li>No events were found.</li>
     {% endfor %}
 </ul>
-
-{% if stream.total_duration %}
-    <hr class="sm">
-    <p class="pl-4 ml-3">Total duration for this stream: <strong>{{ stream.total_duration|duration }}</strong></p>
-{% endif %}
diff --git a/production/templates/production/partials/production_stream_card.html b/production/templates/production/partials/production_stream_card.html
index 8550fa1a34c12ee7acc4e8baadc082c9adee69be..85473b2a34db7edfd56af09298e575b9c3a9df72 100644
--- a/production/templates/production/partials/production_stream_card.html
+++ b/production/templates/production/partials/production_stream_card.html
@@ -1,46 +1,111 @@
 {% extends 'production/partials/production_stream_card_completed.html' %}
 
 {% load bootstrap %}
-{% load guardian_tags %}
-
-{% get_obj_perms request.user for stream as "sub_perms" %}
+{% load scipost_extras %}
 
 {% block actions %}
+    {% include 'production/partials/stream_status_changes.html' with form=status_form stream=stream %}
+
   <h3>Events</h3>
   {% include 'production/partials/production_events.html' with events=stream.events.all %}
 
-    {% if "can_perform_supervisory_actions" in sub_perms %}
+  {% if "can_work_for_stream" in sub_perms and prodevent_form %}
+    <h3>Add message to the Stream</h3>
+    <form action="{% url 'production:add_event' stream_id=stream.id %}" method="post" class="mb-2">
+        {% csrf_token %}
+        {{ prodevent_form|bootstrap }}
+        <input type="submit" class="btn btn-secondary" name="submit" value="Submit">
+    </form>
+  {% endif %}
+
+  <h3>Work Log</h3>
+  {% if "can_work_for_stream" in sub_perms and work_log_form %}
+    <ul>
+        <li>
+            <a href="javascript:;" data-toggle="toggle" data-target="#log_form">Add hours to the Stream</a>
+            <form id="log_form" style="display: none;" action="{% url 'production:add_work_log' stream_id=stream.id %}" method="post" class="mb-2">
+                {% csrf_token %}
+                {{ work_log_form|bootstrap }}
+                <input type="submit" class="btn btn-secondary" name="submit" value="Log">
+            </form>
+        </li>
+    </ul>
+  {% endif %}
+
+  {% include 'partials/finances/logs.html' with logs=stream.work_logs.all %}
+
+  {% if stream.total_duration %}
+      <hr>
+      <p class="text-right">Total duration for this stream: <strong>{{ stream.total_duration|duration }}</strong></p>
+  {% endif %}
+
+    {% if "can_perform_supervisory_actions" in sub_perms or "can_work_for_stream" in sub_perms %}
           <h3>Actions</h3>
           <ul>
-              {% if perms.scipost.can_assign_production_supervisor and assign_supervisor_form %}
-                  <li>
-                      <a href="javascript:;" data-toggle="toggle" data-target="#add_supervisor_{{stream.id}}">Assign Production Supervisor to this stream</a>
-                      <div id="add_supervisor_{{stream.id}}" style="display: none;">
-                          <form class="my-3" action="{% url 'production:add_supervisor' stream_id=stream.id %}" method="post">
-                              {% csrf_token %}
-                              {{ assign_supervisor_form|bootstrap_inline }}
-                              <input type="submit" class="btn btn-outline-primary" name="submit" value="Add officer">
-                          </form>
-                      </div>
-                  </li>
+              {% if "can_perform_supervisory_actions" in sub_perms %}
+                  {% if perms.scipost.can_assign_production_supervisor and assign_supervisor_form %}
+                      <li>
+                          <a href="javascript:;" data-toggle="toggle" data-target="#add_supervisor_{{stream.id}}">Assign Production Supervisor to this stream</a>
+                          <div id="add_supervisor_{{stream.id}}" style="display: none;">
+                              <form class="my-3" action="{% url 'production:add_supervisor' stream_id=stream.id %}" method="post">
+                                  {% csrf_token %}
+                                  {{ assign_supervisor_form|bootstrap_inline }}
+                                  <input type="submit" class="btn btn-outline-primary" name="submit" value="Add supervisor">
+                              </form>
+                          </div>
+                      </li>
+                  {% endif %}
+                  {% if perms.scipost.can_assign_production_officer %}
+                      {% if assign_officer_form %}
+                        <li>
+                            <a href="javascript:;" data-toggle="toggle" data-target="#add_officer_{{stream.id}}">Assign Production Officer to this stream</a>
+                            <div id="add_officer_{{stream.id}}" style="display: none;">
+                                <form class="my-3" action="{% url 'production:add_officer' stream_id=stream.id %}" method="post">
+                                  	{% csrf_token %}
+                                  	{{ assign_officer_form|bootstrap_inline }}
+                                  	<input type="submit" class="btn btn-outline-primary" name="submit" value="Add officer">
+                                </form>
+                            </div>
+                        </li>
+                    {% endif %}
+                    {% if assign_officer_form %}
+                        <li>
+                            <a href="javascript:;" data-toggle="toggle" data-target="#add_invs_officer_{{stream.id}}">Assign Invitations Officer to this stream</a>
+                            <div id="add_invs_officer_{{stream.id}}" style="display: none;">
+                                <form class="my-3" action="{% url 'production:add_invitations_officer' stream_id=stream.id %}" method="post">
+                                  	{% csrf_token %}
+                                  	{{ assign_invitiations_officer_form|bootstrap_inline }}
+                                  	<input type="submit" class="btn btn-outline-primary" name="submit" value="Add officer">
+                                </form>
+                            </div>
+                        </li>
+                    {% endif %}
+                  {% endif %}
               {% endif %}
-              {% if perms.scipost.can_assign_production_officer and assign_officer_form %}
-                <li>
-                    <a href="javascript:;" data-toggle="toggle" data-target="#add_officer_{{stream.id}}">Assign Production Officer to this stream</a>
-                    <div id="add_officer_{{stream.id}}" style="display: none;">
-                        <form class="my-3" action="{% url 'production:add_officer' stream_id=stream.id %}" method="post">
-                          	{% csrf_token %}
-                          	{{ assign_officer_form|bootstrap_inline }}
-                          	<input type="submit" class="btn btn-outline-primary" name="submit" value="Add officer">
-                        </form>
-                    </div>
-                </li>
+
+              {% if "can_work_for_stream" in sub_perms %}
+                  {% if perms.scipost.can_upload_proofs and upload_proofs_form %}
+                      <li>
+                          <a href="javascript:;" data-toggle="toggle" data-target="#upload_proofs">Upload Proofs</a>
+                          <div id="upload_proofs" style="display: none;">
+                              <form class="my-3" action="{% url 'production:upload_proofs' stream_id=stream.id %}" method="post" enctype="multipart/form-data">
+                                	{% csrf_token %}
+                                	{{ upload_proofs_form|bootstrap_inline }}
+                                	<input type="submit" class="btn btn-outline-primary" name="submit" value="Upload">
+                              </form>
+                          </div>
+                      </li>
+                  {% endif %}
               {% endif %}
+
               {% if perms.scipost.can_publish_accepted_submission %}
-                <li><a href="{% url 'production:mark_as_completed' stream_id=stream.id %}">Mark this stream as completed</a></li>
+                  {% if not stream.submission.publication %}
+                    <li><a href="{% url 'journals:initiate_publication' %}">Initiate the publication process</a></li>
+                  {% endif %}
+                    <li><a href="{% url 'production:mark_as_completed' stream_id=stream.id %}">Mark this stream as completed</a></li>
               {% endif %}
           </ul>
-      {% endif %}
+    {% endif %}
 {% endblock %}
 
 {% block officers %}
@@ -57,11 +122,21 @@
     <li>Production Officer:
           {% if stream.officer %}
               <strong>{{ stream.officer }}</strong>
-              {% if perms.scipost.can_assign_production_officer and "can_perform_supervisory_actions" in sub_perms %}
+              {% if "can_work_for_stream" in sub_perms and perms.scipost.can_assign_production_officer %}
                   &middot; <a href="{% url 'production:remove_officer' stream_id=stream.id officer_id=stream.officer.id %}" class="text-danger">Remove from stream</a>
               {% endif %}
           {% else %}
               <em>No Officer assigned yet.</em>
           {% endif %}
     </li>
+    <li>Invitations Officer:
+          {% if stream.invitations_officer %}
+              <strong>{{ stream.invitations_officer }}</strong>
+              {% if "can_work_for_stream" in sub_perms and perms.scipost.can_assign_production_officer %}
+                  &middot; <a href="{% url 'production:remove_invitations_officer' stream_id=stream.id officer_id=stream.invitations_officer.id %}" class="text-danger">Remove from stream</a>
+              {% endif %}
+          {% else %}
+              <em>No Invitations Officer assigned yet.</em>
+          {% endif %}
+    </li>
 {% endblock %}
diff --git a/production/templates/production/partials/production_stream_card_completed.html b/production/templates/production/partials/production_stream_card_completed.html
index f31b37b1a2d379c05a24c49019c45c0fc7f81d06..19eb76bb651c36fc534b62fcd830b29da70ae657 100644
--- a/production/templates/production/partials/production_stream_card_completed.html
+++ b/production/templates/production/partials/production_stream_card_completed.html
@@ -1,16 +1,16 @@
 {% load bootstrap %}
 {% load guardian_tags %}
+{% load scipost_extras %}
 
 {% get_obj_perms request.user for stream as "sub_perms" %}
 
-<div class="w-100" id="stream_{{stream.id}}">
+<div class="card-body py-0" id="stream_{{stream.id}}">
     {% include 'submissions/_submission_card_content_sparse.html' with submission=stream.submission %}
 </div>
 <div class="card-body">
-  <div class="row">
-    <div class="{% if prodevent_form %}col-lg-7{% else %}col-12{% endif %}">
-      <h3>Officers</h3>
+      <h3>Stream details</h3>
       <ul>
+          <li>Status: <span class="label label-secondary label-sm">{{ stream.get_status_display }}</span></li>
           {% block officers %}
               <li>Production Supervisor:
                     {% if stream.supervisor %}
@@ -26,24 +26,57 @@
                         <em>No Officer assigned.</em>
                     {% endif %}
               </li>
+              <li>Invitations Officer:
+                    {% if stream.invitations_officer %}
+                        <strong>{{ stream.invitations_officer }}</strong>
+                    {% else %}
+                        <em>No Invitations Officer assigned.</em>
+                    {% endif %}
+              </li>
           {% endblock %}
       </ul>
 
       {% block actions %}
           <h3>Events</h3>
           {% include 'production/partials/production_events.html' with events=stream.events.all non_editable=1 %}
+
+          <h3>Work Log</h3>
+          {% include 'partials/finances/logs.html' with logs=stream.work_logs.all %}
+
+            {% if stream.total_duration %}
+                <hr class="sm">
+                <p class="pl-4 ml-3">Total duration for this stream: <strong>{{ stream.total_duration|duration }}</strong></p>
+            {% endif %}
       {% endblock %}
 
-  </div>
-    {% if prodevent_form and "can_work_for_stream" in sub_perms %}
-        <div class="col-lg-5 mt-4 mt-lg-0">
-          <h3>Add an event to this production stream:</h3>
-          <form action="{% url 'production:add_event' stream_id=stream.id %}" method="post">
-        	{% csrf_token %}
-        	{{ prodevent_form|bootstrap }}
-        	<input type="submit" class="btn btn-secondary" name="submit" value="Submit">
-          </form>
-        </div>
+    {% if "can_work_for_stream" in sub_perms %}
+      <h3>Proofs</h3>
+      <ul>
+          {% for proofs in stream.proofs.all %}
+              <li class="py-1">
+                  <a href="{% url 'production:proofs' stream_id=stream.id version=proofs.version %}">Version {{ proofs.version }}</a><br>
+                  Uploaded by {{ proofs.uploaded_by.user.first_name }} {{ proofs.uploaded_by.user.last_name }}<br>
+                  Accessible for authors: {{ proofs.accessible_for_authors|yesno:'<strong>Yes</strong>,No'|safe }}<br>
+
+                  {% if perms.scipost.can_run_proofs_by_authors %}
+                      {% if proofs.status == 'uploaded' %}
+                          <strong><span class="text-danger">See details for open actions:</span></strong>
+                          <ul>
+                              <li><a href="{% url 'production:proofs' stream_id=stream.id version=proofs.version %}">Accept or decline proofs</a></li>
+                          </ul>
+                      {% elif proofs.status == 'accepted_sup' %}
+                         <strong><span class="text-danger">See details for open actions:</span></strong>
+                         <ul>
+                             <li><a href="{% url 'production:proofs' stream_id=stream.id version=proofs.version %}">Send proofs to authors</a></li>
+                         </ul>
+                      {% endif %}
+                  {% endif %}
+
+                  <span class="label label-secondary label-sm">{{ proofs.get_status_display }}</span>
+              </li>
+          {% empty %}
+              <li>No Proofs found.</li>
+          {% endfor %}
+      </ul>
     {% endif %}
-  </div>
 </div>
diff --git a/production/templates/production/partials/stream_status_changes.html b/production/templates/production/partials/stream_status_changes.html
new file mode 100644
index 0000000000000000000000000000000000000000..6ade74af290681a717806091502ddbde14db562f
--- /dev/null
+++ b/production/templates/production/partials/stream_status_changes.html
@@ -0,0 +1,14 @@
+{% load bootstrap %}
+
+{% if perms.scipost.can_take_decisions_related_to_proofs and form.fields.status.choices|length > 0 %}
+    <h3>Change current stream status:</h3>
+    <form method="post" action="{% url 'production:update_status' stream.id %}" class="form-inline">
+        {% csrf_token %}
+        {{ form|bootstrap_inline }}
+        <div class="form-group row">
+            <div class="col-form-label col ml-2">
+                <button type="submit" class="btn btn-primary">Change</button>
+            </div>
+        </div>
+    </form>
+{% endif %}
diff --git a/production/templates/production/production.html b/production/templates/production/production.html
index f9a693d6e9f132a36a469f1e2e70d013c37e4f63..844922fd09730361c68469c5a446ac6493eb5201 100644
--- a/production/templates/production/production.html
+++ b/production/templates/production/production.html
@@ -7,6 +7,7 @@
 {% block pagetitle %}: Production page{% endblock pagetitle %}
 
 {% load bootstrap %}
+{% load scipost_extras %}
 
 {% block content %}
 
@@ -49,38 +50,53 @@
       </div>
     </div>
 
-    <table class="table table-hover mb-5">
-      <thead class="thead-default">
-	<tr>
-	  <th>Authors</th>
-	  <th>Title</th>
-	  <th>Accepted</th>
-	  <th>Latest Event</th>
-	  <th>Date</th>
-	</tr>
-      </thead>
-
-      <tbody id="accordion" role="tablist" aria-multiselectable="true">
-    	{% for stream in streams %}
-        	<tr data-toggle="collapse" data-parent="#accordion" href="#collapse{{ stream.id }}" aria-expanded="true" aria-controls="collapse{{ stream.id }}" style="cursor: pointer;">
-        	  <td>{{ stream.submission.author_list }}</td>
-        	  <td>{{ stream.submission.title }}</td>
-        	  <td>{{ stream.submission.acceptance_date|date:"Y-m-d" }}</td>
-        	  <td>{{ stream.events.last.get_event_display }}</td>
-        	  <td>{{ stream.events.last.noted_on }}</td>
-        	</tr>
-        	<tr id="collapse{{ stream.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ stream.id }}" style="background-color: #fff;">
-        	  <td colspan="5">
-                    {% include 'production/partials/production_stream_card.html' with stream=stream prodevent_form=prodevent_form assignment_form=assignment_form %}
-        	  </td>
-        	</tr>
-    	{% empty %}
-        	<tr>
-        	  <td colspan="5">No production streams found.</td>
-        	</tr>
-    	{% endfor %}
-      </tbody>
-    </table>
+    <div class="row">
+        <div class="col-6">
+            <ul class="list-unstyled" data-target="active-list">
+                {% for stream in streams %}
+                    <li class="p-2">
+                        <div class="d-flex justify-content-start">
+                            <div class="icons pr-2">
+                                <i class="fa fa-users" data-toggle="tooltip" data-html="true" title="" data-original-title="
+                                    Supervisor: {% if stream.supervisor %}{{ stream.supervisor.user.first_name }} {{ stream.supervisor.user.last_name }}{% else %}<em>Unassigned</em>{% endif %}<br>
+                                    Officer: {% if stream.officer %}{{ stream.officer.user.first_name }} {{ stream.officer.user.last_name }}{% else %}<em>Unassigned</em>{% endif %}
+                                "></i>
+                                {% if stream.supervisor.user == request.user %}
+                                    <i class="fa fa-info-circle" data-toggle="tooltip" data-html="true" title="" data-original-title="You are assigned as supervisor"></i>
+                                {% endif %}
+                            </div>
+                            <div>
+                                <p class="mb-1">
+                                    <a href="{% url 'production:stream' stream.id %}">{{ stream.submission.title }}</a><br>
+                                    <em>by {{ stream.submission.author_list }}</em>
+                                </p>
+                                <p class="card-text mb-2">
+                                    <a href="{% url 'production:stream' stream.id %}" data-toggle="dynamic" data-target="#details">See stream details</a>
+                                </p>
+
+                                <p class="label label-{% if stream.status == 'initiated' %}outline-danger{% else %}secondary{% endif %} label-sm">{{ stream.get_status_display }}</p>
+                            </div>
+                        </div>
+                    </li>
+                {% endfor %}
+            </ul>
+
+        </div>
+        <div class="col-6">
+            <div class="card center-loader" id="details">
+                {% if stream %}
+                    {% include 'production/partials/production_stream_card.html' %}
+                {% else %}
+                    <div class="text-center py-4">
+                        <i class="fa fa-hand-o-left fa-3x" aria-hidden="true"></i>
+
+                        <h3>Choose a Stream to see more details</h3>
+                    </div>
+                {% endif %}
+            </div>
+
+        </div>
+    </div>
   </div>
 
 
@@ -98,23 +114,24 @@
       <thead class="thead-default">
 	<tr>
 	  <th>Date</th>
+	  <th>Comment</th>
 	  <th>Stream</th>
-	  <th>Event</th>
+      <th>Log type</th>
 	  <th>Duration</th>
 	</tr>
       </thead>
-
       <tbody role="tablist">
-	{% for event in ownevents %}
-	<tr>
-	  <td>{{ event.noted_on }}</td>
-	  <td>{{ event.stream }}</td>
-	  <td>{{ event.get_event_display }}</td>
-	  <td>{{ event.duration }}</td>
-	</tr>
+	{% for log in request.user.work_logs.all %}
+    	<tr>
+    	  <td>{{ log.work_date }}</td>
+    	  <td>{{ log.comments }}</td>
+    	  <td>{{ log.content }}</td>
+          <td>{{ log.log_type }}</td>
+    	  <td>{{ log.duration|duration }}</td>
+    	</tr>
 	{% empty %}
 	<tr>
-	  <td colspan="4">No events found.</td>
+	  <td colspan="4">No logs found.</td>
 	</tr>
 	{% endfor %}
       </tbody>
diff --git a/production/templates/production/proofs.html b/production/templates/production/proofs.html
new file mode 100644
index 0000000000000000000000000000000000000000..8a2f4071aadfd9296f1939117d332af4933f4738
--- /dev/null
+++ b/production/templates/production/proofs.html
@@ -0,0 +1,50 @@
+{% extends 'production/base.html' %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <a href="{{ stream.get_absolute_url }}" class="breadcrumb-item">Production Stream</a>
+    <span class="breadcrumb-item">Proofs (version {{ proofs.version }})</span>
+{% endblock %}
+
+{% load bootstrap %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Proofs (version {{ proofs.version }})</h1>
+        {% include 'submissions/_submission_card_content_sparse.html' with submission=stream.submission %}
+    </div>
+</div>
+<div class="row">
+    <div class="col-12">
+        <h3>Info</h3>
+        <ul>
+            <li>Version: {{ proofs.version }}</li>
+            <li>Status: <span class="label label-secondary label-sm">{{ proofs.get_status_display }}</span></li>
+            <li>Uploaded by: {{ proofs.uploaded_by }}</li>
+            <li>Accessible for Authors: {{ proofs.accessible_for_authors|yesno:'Yes,No' }}</li>
+        </ul>
+
+        <h3>Actions</h3>
+        <ul>
+            <li><a href="{% url 'production:proofs_pdf' proofs.slug %}" target="_blank">Download file</a></li>
+            {% if perms.scipost.can_run_proofs_by_authors %}
+                {% if proofs.status == 'uploaded' %}
+                    <li>
+                        <a href="{% url 'production:decision' proofs.stream.id proofs.version 'accept' %}">Accept proofs</a>
+                        &middot;
+                        <a href="{% url 'production:decision' proofs.stream.id proofs.version 'decline' %}" class="text-danger">Decline proofs</a>
+                    </li>
+                {% elif proofs.status == 'accepted_sup' %}
+                    <li><a href="{% url 'production:send_proofs' proofs.stream.id proofs.version %}">Send proofs to authors</a></li>
+                {% endif %}
+                {% if proofs.status != 'uploaded' %}
+                    <li><a href="{% url 'production:toggle_accessibility' proofs.stream.id proofs.version %}">{{ proofs.accessible_for_authors|yesno:'Hide,Make accessible' }} for authors</a></li>
+                {% endif %}
+            {% endif %}
+        </ul>
+    </div>
+</div>
+
+{% endblock content %}
diff --git a/production/templates/production/stream.html b/production/templates/production/stream.html
new file mode 100644
index 0000000000000000000000000000000000000000..bc70003ea1f8812f83e4e23788ccee170d589a41
--- /dev/null
+++ b/production/templates/production/stream.html
@@ -0,0 +1,13 @@
+{% extends 'production/base.html' %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <span class="breadcrumb-item">Production Stream</span>
+{% endblock %}
+
+{% block content %}
+
+<h1 class="highlight">Production Stream</h1>
+{% include 'production/partials/production_stream_card.html' %}
+
+{% endblock content %}
diff --git a/production/templates/production/upload_proofs.html b/production/templates/production/upload_proofs.html
new file mode 100644
index 0000000000000000000000000000000000000000..09970ec96a500852c38c7b745db653ec10f0c9d9
--- /dev/null
+++ b/production/templates/production/upload_proofs.html
@@ -0,0 +1,29 @@
+{% extends 'production/base.html' %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <span class="breadcrumb-item">Upload Proofs</span>
+{% endblock %}
+
+{% load bootstrap %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Upload Proofs</h1>
+        {% include 'submissions/_submission_card_content_sparse.html' with submission=stream.submission %}
+    </div>
+</div>
+<div class="row">
+  <div class="col-12">
+      <form method="post" enctype="multipart/form-data">
+        {% csrf_token %}
+        {{ form|bootstrap }}
+        <input type="submit" class="btn btn-secondary" name="submit" value="Upload">
+      </form>
+    </ul>
+  </div>
+</div>
+
+{% endblock content %}
diff --git a/production/urls.py b/production/urls.py
index 8631da56cd28b1f9a99f50f85072d7477ba71204..c44ef6554601b294b125872607bb94f85bd7dcc5 100644
--- a/production/urls.py
+++ b/production/urls.py
@@ -4,14 +4,35 @@ from production import views as production_views
 
 urlpatterns = [
     url(r'^$', production_views.production, name='production'),
+    url(r'^(?P<stream_id>[0-9]+)$', production_views.production, name='production'),
     url(r'^completed$', production_views.completed, name='completed'),
     url(r'^officers/new$', production_views.user_to_officer, name='user_to_officer'),
+    url(r'^streams/(?P<stream_id>[0-9]+)$',
+        production_views.stream, name='stream'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/status$',
+        production_views.update_status, name='update_status'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/proofs/upload$',
+        production_views.upload_proofs, name='upload_proofs'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/proofs/(?P<version>[0-9]+)$',
+        production_views.proofs, name='proofs'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/proofs/(?P<version>[0-9]+)/decision/(?P<decision>accept|decline)$',
+        production_views.decision, name='decision'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/proofs/(?P<version>[0-9]+)/send_to_authors$',
+        production_views.send_proofs, name='send_proofs'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/proofs/(?P<version>[0-9]+)/toggle_access$',
+        production_views.toggle_accessibility, name='toggle_accessibility'),
     url(r'^streams/(?P<stream_id>[0-9]+)/events/add$',
         production_views.add_event, name='add_event'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/logs/add$',
+        production_views.add_work_log, name='add_work_log'),
     url(r'^streams/(?P<stream_id>[0-9]+)/officer/add$',
         production_views.add_officer, name='add_officer'),
     url(r'^streams/(?P<stream_id>[0-9]+)/officer/(?P<officer_id>[0-9]+)/remove$',
         production_views.remove_officer, name='remove_officer'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/invitations_officer/add$',
+        production_views.add_invitations_officer, name='add_invitations_officer'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/invitations_officer/(?P<officer_id>[0-9]+)/remove$',
+        production_views.remove_invitations_officer, name='remove_invitations_officer'),
     url(r'^streams/(?P<stream_id>[0-9]+)/supervisor/add$',
         production_views.add_supervisor, name='add_supervisor'),
     url(r'^streams/(?P<stream_id>[0-9]+)/supervisor/(?P<officer_id>[0-9]+)/remove$',
@@ -22,4 +43,8 @@ urlpatterns = [
         production_views.UpdateEventView.as_view(), name='update_event'),
     url(r'^events/(?P<event_id>[0-9]+)/delete',
         production_views.DeleteEventView.as_view(), name='delete_event'),
+    url(r'^proofs/(?P<slug>[0-9]+)$',
+        production_views.proofs_pdf, name='proofs_pdf'),
+    url(r'^proofs/(?P<slug>[0-9]+)/decision$',
+        production_views.author_decision, name='author_decision'),
 ]
diff --git a/production/utils.py b/production/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..12d31bc778752d05ce0ed9d39cb549bb34c5dfc2
--- /dev/null
+++ b/production/utils.py
@@ -0,0 +1,6 @@
+def proofs_id_to_slug(id):
+    return int(id) + 8932
+
+
+def proofs_slug_to_id(slug):
+    return int(slug) - 8932
diff --git a/production/views.py b/production/views.py
index 64b7d4eb282aadac5ca3586c880efeecbc89afa1..49bfa8926e18bd7ba4c338287cb27d08c1c6110d 100644
--- a/production/views.py
+++ b/production/views.py
@@ -1,23 +1,31 @@
 import datetime
+import mimetypes
 
 from django.contrib import messages
-from django.contrib.auth.decorators import permission_required
+from django.contrib.auth.decorators import login_required, permission_required
 from django.contrib.auth.models import Group
 from django.core.urlresolvers import reverse
 from django.db import transaction
+from django.http import Http404, HttpResponse
 from django.shortcuts import get_object_or_404, render, redirect
 from django.utils import timezone
 from django.utils.decorators import method_decorator
 from django.views.generic.edit import UpdateView, DeleteView
 
 from guardian.core import ObjectPermissionChecker
-from guardian.shortcuts import assign_perm
+from guardian.shortcuts import assign_perm, remove_perm
 
-from .constants import PRODUCTION_STREAM_COMPLETED
-from .models import ProductionUser, ProductionStream, ProductionEvent
-from .forms import ProductionEventForm, AssignOfficerForm, UserToOfficerForm, AssignSupervisorForm
+from finances.forms import WorkLogForm
+from mails.views import MailEditingSubView
+
+from . import constants
+from .models import ProductionUser, ProductionStream, ProductionEvent, Proofs
+from .forms import ProductionEventForm, AssignOfficerForm, UserToOfficerForm,\
+                   AssignSupervisorForm, StreamStatusForm, ProofsUploadForm, ProofsDecisionForm,\
+                   AssignInvitationsOfficerForm
 from .permissions import is_production_user
-from .signals import notify_stream_completed, notify_new_stream_assignment
+from .signals import notify_stream_status_change,  notify_new_stream_assignment
+from .utils import proofs_slug_to_id
 
 
 ######################
@@ -26,7 +34,7 @@ from .signals import notify_stream_completed, notify_new_stream_assignment
 
 @is_production_user()
 @permission_required('scipost.can_view_production', raise_exception=True)
-def production(request):
+def production(request, stream_id=None):
     """
     Overview page for the production process.
     All papers with accepted but not yet published status are included here.
@@ -37,19 +45,28 @@ def production(request):
         streams = streams.filter_for_user(request.user.production_user)
     streams = streams.order_by('opened')
 
-    prodevent_form = ProductionEventForm()
-    assign_officer_form = AssignOfficerForm()
-    assign_supervisor_form = AssignSupervisorForm()
-    ownevents = ProductionEvent.objects.filter(
-        noted_by=request.user.production_user,
-        duration__gte=datetime.timedelta(minutes=1)).order_by('-noted_on')
     context = {
         'streams': streams,
-        'prodevent_form': prodevent_form,
-        'assign_officer_form': assign_officer_form,
-        'assign_supervisor_form': assign_supervisor_form,
-        'ownevents': ownevents,
     }
+
+    if stream_id:
+        try:
+            # "Pre-load" ProductionStream
+            context['stream'] = streams.get(id=stream_id)
+            context['assign_officer_form'] = AssignOfficerForm()
+            context['assign_invitiations_officer_form'] = AssignInvitationsOfficerForm()
+            context['assign_supervisor_form'] = AssignSupervisorForm()
+            context['prodevent_form'] = ProductionEventForm()
+
+            if request.user.has_perm('scipost.can_view_all_production_streams'):
+                types = constants.PRODUCTION_ALL_WORK_LOG_TYPES
+            else:
+                types = constants.PRODUCTION_OFFICERS_WORK_LOG_TYPES
+            context['work_log_form'] = WorkLogForm(log_types=types)
+            context['upload_proofs_form'] = ProofsUploadForm()
+        except ProductionStream.DoesNotExist:
+            pass
+
     if request.user.has_perm('scipost.can_view_timesheets'):
         context['production_team'] = ProductionUser.objects.all()
 
@@ -74,6 +91,47 @@ def completed(request):
     return render(request, 'production/completed.html', context)
 
 
+@is_production_user()
+@permission_required('scipost.can_view_production', raise_exception=True)
+def stream(request, stream_id):
+    """
+    Overview page for specific stream.
+    """
+    streams = ProductionStream.objects.ongoing()
+    if not request.user.has_perm('scipost.can_view_all_production_streams'):
+        # Restrict stream queryset if user is not supervisor
+        streams = streams.filter_for_user(request.user.production_user)
+    stream = get_object_or_404(streams, id=stream_id)
+    prodevent_form = ProductionEventForm()
+    assign_officer_form = AssignOfficerForm()
+    assign_invitiations_officer_form = AssignInvitationsOfficerForm()
+    assign_supervisor_form = AssignSupervisorForm()
+    upload_proofs_form = ProofsUploadForm()
+
+    if request.user.has_perm('scipost.can_view_all_production_streams'):
+        types = constants.PRODUCTION_ALL_WORK_LOG_TYPES
+    else:
+        types = constants.PRODUCTION_OFFICERS_WORK_LOG_TYPES
+    work_log_form = WorkLogForm(log_types=types)
+    status_form = StreamStatusForm(instance=stream, production_user=request.user.production_user)
+
+    context = {
+        'stream': stream,
+        'prodevent_form': prodevent_form,
+        'assign_officer_form': assign_officer_form,
+        'assign_supervisor_form': assign_supervisor_form,
+        'assign_invitiations_officer_form': assign_invitiations_officer_form,
+        'status_form': status_form,
+        'upload_proofs_form': upload_proofs_form,
+        'work_log_form': work_log_form,
+    }
+
+    if request.GET.get('json'):
+        return render(request, 'production/partials/production_stream_card.html', context)
+    else:
+        return render(request, 'production/stream.html', context)
+
+
 @is_production_user()
 @permission_required('scipost.can_promote_user_to_production_officer')
 def user_to_officer(request):
@@ -88,6 +146,26 @@ def user_to_officer(request):
     return redirect(reverse('production:production'))
 
 
+@is_production_user()
+@permission_required('scipost.can_take_decisions_related_to_proofs', raise_exception=True)
+def update_status(request, stream_id):
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm('can_perform_supervisory_actions', stream):
+        return redirect(reverse('production:production', args=(stream.id,)))
+
+    p = request.user.production_user
+    form = StreamStatusForm(request.POST or None, instance=stream,
+                            production_user=p)
+
+    if form.is_valid():
+        stream = form.save()
+        messages.warning(request, 'Production Stream succesfully changed status.')
+    else:
+        messages.warning(request, 'The status change was invalid.')
+    return redirect(stream.get_absolute_url())
+
+
 @is_production_user()
 @permission_required('scipost.can_view_production', raise_exception=True)
 def add_event(request, stream_id):
@@ -102,18 +180,45 @@ def add_event(request, stream_id):
         prodevent.stream = stream
         prodevent.noted_by = request.user.production_user
         prodevent.save()
+        messages.success(request, 'Comment added to Stream.')
     else:
         messages.warning(request, 'The form was invalidly filled.')
-    return redirect(reverse('production:production'))
+    return redirect(reverse('production:production', args=(stream.id,)))
+
+
+@is_production_user()
+@permission_required('scipost.can_view_production', raise_exception=True)
+def add_work_log(request, stream_id):
+    stream = get_object_or_404(ProductionStream, pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm('can_work_for_stream', stream):
+        return redirect(stream.get_absolute_url())
+
+    if request.user.has_perm('scipost.can_view_all_production_streams'):
+        types = constants.PRODUCTION_ALL_WORK_LOG_TYPES
+    else:
+        types = constants.PRODUCTION_OFFICERS_WORK_LOG_TYPES
+    work_log_form = WorkLogForm(request.POST or None, log_types=types)
+
+    if work_log_form.is_valid():
+        log = work_log_form.save(commit=False)
+        log.content = stream
+        log.user = request.user
+        log.save()
+        messages.success(request, 'Work Log added to Stream.')
+    else:
+        messages.warning(request, 'The form was invalidly filled.')
+    return redirect(stream.get_absolute_url())
 
 
 @is_production_user()
 @permission_required('scipost.can_assign_production_officer', raise_exception=True)
+@transaction.atomic
 def add_officer(request, stream_id):
     stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
     checker = ObjectPermissionChecker(request.user)
     if not checker.has_perm('can_perform_supervisory_actions', stream):
-        return redirect(reverse('production:production'))
+        return redirect(reverse('production:production', args=(stream.id,)))
 
     form = AssignOfficerForm(request.POST or None, instance=stream)
     if form.is_valid():
@@ -122,27 +227,90 @@ def add_officer(request, stream_id):
         assign_perm('can_work_for_stream', officer.user, stream)
         messages.success(request, 'Officer {officer} has been assigned.'.format(officer=officer))
         notify_new_stream_assignment(request.user, stream, officer.user)
+        event = ProductionEvent(
+            stream=stream,
+            event='assignment',
+            comments=' tasked Production Officer with proofs production:',
+            noted_to=officer,
+            noted_by=request.user.production_user)
+        event.save()
     else:
         for key, error in form.errors.items():
             messages.warning(request, error[0])
-    return redirect(reverse('production:production'))
+    return redirect(reverse('production:production', args=(stream.id,)))
 
 
 @is_production_user()
 @permission_required('scipost.can_assign_production_officer', raise_exception=True)
+@transaction.atomic
+def add_invitations_officer(request, stream_id):
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm('can_perform_supervisory_actions', stream):
+        return redirect(reverse('production:production', args=(stream.id,)))
+
+    form = AssignInvitationsOfficerForm(request.POST or None, instance=stream)
+    if form.is_valid():
+        form.save()
+        officer = form.cleaned_data.get('invitations_officer')
+        assign_perm('can_work_for_stream', officer.user, stream)
+        messages.success(request, 'Invitations Officer {officer} has been assigned.'.format(
+            officer=officer))
+        notify_new_stream_assignment(request.user, stream, officer.user)
+        event = ProductionEvent(
+            stream=stream,
+            event='assignment',
+            comments=' tasked Invitations Officer with invitations:',
+            noted_to=officer,
+            noted_by=request.user.production_user)
+        event.save()
+    else:
+        for key, error in form.errors.items():
+            messages.warning(request, error[0])
+    return redirect(reverse('production:production', args=(stream.id,)))
+
+
+@is_production_user()
+@permission_required('scipost.can_assign_production_officer', raise_exception=True)
+@transaction.atomic
 def remove_officer(request, stream_id, officer_id):
     stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
     checker = ObjectPermissionChecker(request.user)
     if not checker.has_perm('can_perform_supervisory_actions', stream):
-        return redirect(reverse('production:production'))
+        return redirect(reverse('production:production', args=(stream.id,)))
 
     if getattr(stream.officer, 'id', 0) == int(officer_id):
         officer = stream.officer
         stream.officer = None
         stream.save()
+        if officer not in [stream.invitations_officer, stream.supervisor]:
+            # Remove Officer from stream if not assigned anymore
+            remove_perm('can_work_for_stream', officer.user, stream)
         messages.success(request, 'Officer {officer} has been removed.'.format(officer=officer))
 
-    return redirect(reverse('production:production'))
+    return redirect(reverse('production:production', args=(stream.id,)))
+
+
+@is_production_user()
+@permission_required('scipost.can_assign_production_officer', raise_exception=True)
+@transaction.atomic
+def remove_invitations_officer(request, stream_id, officer_id):
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm('can_perform_supervisory_actions', stream):
+        return redirect(reverse('production:production', args=(stream.id,)))
+
+    if getattr(stream.invitations_officer, 'id', 0) == int(officer_id):
+        officer = stream.invitations_officer
+        stream.invitations_officer = None
+        stream.save()
+        if officer not in [stream.officer, stream.supervisor]:
+            # Remove Officer from stream if not assigned anymore
+            remove_perm('can_work_for_stream', officer.user, stream)
+        messages.success(request, 'Invitations Officer {officer} has been removed.'.format(
+            officer=officer))
+
+    return redirect(reverse('production:production', args=(stream.id,)))
 
 
 @is_production_user()
@@ -154,29 +322,40 @@ def add_supervisor(request, stream_id):
     if form.is_valid():
         form.save()
         supervisor = form.cleaned_data.get('supervisor')
-        assign_perm('can_work_for_stream', supervisor.user, stream)
-        assign_perm('can_perform_supervisory_actions', supervisor.user, stream)
         messages.success(request, 'Supervisor {supervisor} has been assigned.'.format(
             supervisor=supervisor))
         notify_new_stream_assignment(request.user, stream, supervisor.user)
+        event = ProductionEvent(
+            stream=stream,
+            event='assignment',
+            comments=' assigned Production Supervisor:',
+            noted_to=supervisor,
+            noted_by=request.user.production_user)
+        event.save()
+
+        assign_perm('can_work_for_stream', supervisor.user, stream)
+        assign_perm('can_perform_supervisory_actions', supervisor.user, stream)
     else:
         for key, error in form.errors.items():
             messages.warning(request, error[0])
-    return redirect(reverse('production:production'))
+    return redirect(reverse('production:production', args=(stream.id,)))
 
 
 @is_production_user()
 @permission_required('scipost.can_assign_production_supervisor', raise_exception=True)
+@transaction.atomic
 def remove_supervisor(request, stream_id, officer_id):
     stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
     if getattr(stream.supervisor, 'id', 0) == int(officer_id):
         supervisor = stream.supervisor
         stream.supervisor = None
         stream.save()
+        remove_perm('can_work_for_stream', supervisor.user, stream)
+        remove_perm('can_perform_supervisory_actions', supervisor.user, stream)
         messages.success(request, 'Supervisor {supervisor} has been removed.'.format(
             supervisor=supervisor))
 
-    return redirect(reverse('production:production'))
+    return redirect(reverse('production:production', args=(stream.id,)))
 
 
 @method_decorator(is_production_user(), name='dispatch')
@@ -220,18 +399,248 @@ class DeleteEventView(DeleteView):
 @transaction.atomic
 def mark_as_completed(request, stream_id):
     stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
-    stream.status = PRODUCTION_STREAM_COMPLETED
+    stream.status = constants.PRODUCTION_STREAM_COMPLETED
     stream.closed = timezone.now()
     stream.save()
 
-    notify_stream_completed(request.user, stream)
+    prodevent = ProductionEvent(
+        stream=stream,
+        event='status',
+        comments=' marked the Production Stream as completed.',
+        noted_by=request.user.production_user
+    )
+    prodevent.save()
+    notify_stream_status_change(request.user, stream, True)
+    messages.success(request, 'Stream marked as completed.')
     return redirect(reverse('production:production'))
 
 
-def upload_proofs(request):
+@is_production_user()
+@permission_required('scipost.can_upload_proofs', raise_exception=True)
+@transaction.atomic
+def upload_proofs(request, stream_id):
+    """
+    Called by a member of the Production Team.
+    Upload the production version .pdf of a submission.
+    """
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm('can_work_for_stream', stream):
+        return redirect(reverse('production:production'))
+
+    form = ProofsUploadForm(request.POST or None, request.FILES or None)
+    if form.is_valid():
+        proofs = form.save(commit=False)
+        proofs.stream = stream
+        proofs.uploaded_by = request.user.production_user
+        proofs.save()
+        Proofs.objects.filter(stream=stream).exclude(version=proofs.version).exclude(
+            status=constants.PROOFS_ACCEPTED).update(status=constants.PROOFS_RENEWED)
+        messages.success(request, 'Proof uploaded.')
+
+        # Update Stream status
+        if stream.status == constants.PROOFS_TASKED:
+            stream.status = constants.PROOFS_PRODUCED
+            stream.save()
+        elif stream.status == constants.PROOFS_RETURNED:
+            stream.status = constants.PROOFS_CORRECTED
+            stream.save()
+
+        prodevent = ProductionEvent(
+            stream=stream,
+            event='status',
+            comments='New Proofs uploaded, version {v}'.format(v=proofs.version),
+            noted_by=request.user.production_user
+        )
+        prodevent.save()
+        return redirect(stream.get_absolute_url())
+
+    context = {
+        'stream': stream,
+        'form': form
+    }
+    return render(request, 'production/upload_proofs.html', context)
+
+
+@is_production_user()
+@permission_required('scipost.can_view_production', raise_exception=True)
+def proofs(request, stream_id, version):
     """
-    TODO
     Called by a member of the Production Team.
     Upload the production version .pdf of a submission.
     """
-    return render(request, 'production/upload_proofs.html')
+    stream = get_object_or_404(ProductionStream.objects.all(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm('can_work_for_stream', stream):
+        return redirect(reverse('production:production'))
+
+    try:
+        proofs = stream.proofs.get(version=version)
+    except Proofs.DoesNotExist:
+        raise Http404
+
+    context = {
+        'stream': stream,
+        'proofs': proofs
+    }
+    return render(request, 'production/proofs.html', context)
+
+
+def proofs_pdf(request, slug):
+    """ Open Proofs pdf. """
+    if not request.user.is_authenticated:
+        # Don't use the decorator but this strategy,
+        # because now it will return 404 instead of a redirect to the login page.
+        raise Http404
+
+    proofs = Proofs.objects.get(id=proofs_slug_to_id(slug))
+    stream = proofs.stream
+
+    # Check if user has access!
+    checker = ObjectPermissionChecker(request.user)
+    access = checker.has_perm('can_work_for_stream', stream) and request.user.has_perm('scipost.can_view_production')
+    if not access and request.user.contributor:
+        access = request.user.contributor in proofs.stream.submission.authors.all()
+    if not access:
+        raise Http404
+
+    # Passed the test! The user may see the file!
+    content_type, encoding = mimetypes.guess_type(proofs.attachment.path)
+    content_type = content_type or 'application/octet-stream'
+    response = HttpResponse(proofs.attachment.read(), content_type=content_type)
+    response["Content-Encoding"] = encoding
+    return response
+
+
+@login_required
+@transaction.atomic
+def author_decision(request, slug):
+    """
+    The authors of a Submission/Proof are asked for their decision on the proof.
+    Accept or Decline? This will be asked if proof status is `ACCEPTED_SUP` and
+    will be handled in this view.
+    """
+    proofs = Proofs.objects.get(id=proofs_slug_to_id(slug))
+    stream = proofs.stream
+
+    # Check if user has access!
+    if request.user.contributor not in proofs.stream.submission.authors.all():
+        raise Http404
+
+    form = ProofsDecisionForm(request.POST or None, instance=proofs)
+    if form.is_valid():
+        proofs = form.save()
+        notify_stream_status_change(request.user, stream, False)
+        messages.success(request, 'Your decision has been sent.')
+
+    return redirect(stream.submission.get_absolute_url())
+
+
+@is_production_user()
+@permission_required('scipost.can_run_proofs_by_authors', raise_exception=True)
+def toggle_accessibility(request, stream_id, version):
+    """
+    Open/close accessibility of proofs to the authors.
+    """
+    stream = get_object_or_404(ProductionStream.objects.all(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm('can_work_for_stream', stream):
+        return redirect(reverse('production:production'))
+
+    try:
+        proofs = stream.proofs.exclude(status=constants.PROOFS_UPLOADED).get(version=version)
+    except Proofs.DoesNotExist:
+        raise Http404
+
+    proofs.accessible_for_authors = not proofs.accessible_for_authors
+    proofs.save()
+    messages.success(request, 'Proofs accessibility updated.')
+    return redirect(stream.get_absolute_url())
+
+
+@is_production_user()
+@permission_required('scipost.can_run_proofs_by_authors', raise_exception=True)
+@transaction.atomic
+def decision(request, stream_id, version, decision):
+    """
+    Send/open proofs to the authors. This decision is taken by the supervisor.
+    """
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm('can_work_for_stream', stream):
+        return redirect(reverse('production:production'))
+
+    try:
+        proofs = stream.proofs.get(version=version, status=constants.PROOFS_UPLOADED)
+    except Proofs.DoesNotExist:
+        raise Http404
+
+    if decision == 'accept':
+        proofs.status = constants.PROOFS_ACCEPTED_SUP
+        stream.status = constants.PROOFS_CHECKED
+        decision = 'accepted'
+    else:
+        proofs.status = constants.PROOFS_DECLINED_SUP
+        proofs.accessible_for_authors = False
+        stream.status = constants.PROOFS_TASKED
+        decision = 'declined'
+    stream.save()
+    proofs.save()
+
+    prodevent = ProductionEvent(
+        stream=stream,
+        event='status',
+        comments='Proofs version {version} are {decision}.'.format(version=proofs.version,
+                                                                   decision=decision),
+        noted_by=request.user.production_user
+    )
+    prodevent.save()
+    messages.success(request, 'Proofs have been {decision}.'.format(decision=decision))
+    return redirect(stream.get_absolute_url())
+
+
+@is_production_user()
+@permission_required('scipost.can_run_proofs_by_authors', raise_exception=True)
+@transaction.atomic
+def send_proofs(request, stream_id, version):
+    """
+    Send/open proofs to the authors.
+    """
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm('can_work_for_stream', stream):
+        return redirect(reverse('production:production'))
+
+    try:
+        proofs = stream.proofs.can_be_send().get(version=version)
+    except Proofs.DoesNotExist:
+        raise Http404
+
+    proofs.status = constants.PROOFS_SENT
+    proofs.accessible_for_authors = True
+
+    if stream.status not in [constants.PROOFS_PUBLISHED, constants.PROOFS_CITED,
+                             constants.PRODUCTION_STREAM_COMPLETED]:
+        stream.status = constants.PROOFS_SENT
+        stream.save()
+
+    mail_request = MailEditingSubView(request, mail_code='production_send_proofs',
+                                      proofs=proofs)
+    if mail_request.is_valid():
+        proofs.save()
+        stream.save()
+        messages.success(request, 'Proofs have been sent.')
+        mail_request.send()
+        prodevent = ProductionEvent(
+            stream=stream,
+            event='status',
+            comments='Proofs version {version} sent to authors.'.format(version=proofs.version),
+            noted_by=request.user.production_user
+        )
+        prodevent.save()
+        return redirect(stream.get_absolute_url())
+    else:
+        return mail_request.return_render()
+
+    messages.success(request, 'Proofs have been sent.')
+    return redirect(stream.get_absolute_url())
diff --git a/requirements.txt b/requirements.txt
index c86df0eaf9d69e3b52f65829395321fdc763bcbf..0cb1a60d9d08cddb4f124bfd6db4574d220c53ab 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -48,7 +48,6 @@ mailchimp3==2.0.15
 python-dateutil==2.6.0  # Doesn't Django have this functionality built-in?  -- JdW
 Pillow==3.4.2  # Latest version is v4.2.1; need to know about usage before upgrade. -- JdW
 
-
 # Possibly dead (most probably not used anymore and possibly not up-to-date packages)  -- JdW (August 15th, 2017)
 imagesize==0.7.1
 Jinja2==2.8
diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py
index 638a67dfa9feefffa42216af275ff77ad80cd5f7..6e3067a54ec7c406d004e45ebeedbbd14879203a 100644
--- a/scipost/management/commands/add_groups_and_permissions.py
+++ b/scipost/management/commands/add_groups_and_permissions.py
@@ -226,6 +226,18 @@ class Command(BaseCommand):
             codename='can_view_production',
             name='Can view production page',
             content_type=content_type)
+        can_upload_proofs, created = Permission.objects.get_or_create(
+            codename='can_upload_proofs',
+            name='Can upload proofs',
+            content_type=content_type)
+        can_take_decisions_related_to_proofs, created = Permission.objects.get_or_create(
+            codename='can_take_decisions_related_to_proofs',
+            name='Can take decisions related to proofs',
+            content_type=content_type)
+        can_run_proofs_by_authors, created = Permission.objects.get_or_create(
+            codename='can_run_proofs_by_authors',
+            name='Can run proof by authors',
+            content_type=content_type)
         can_publish_accepted_submission, created = Permission.objects.get_or_create(
             codename='can_publish_accepted_submission',
             name='Can publish accepted submission',
@@ -300,6 +312,9 @@ class Command(BaseCommand):
             can_manage_reports,
             can_assign_production_supervisor,
             can_view_all_production_streams,
+            can_take_decisions_related_to_proofs,
+            can_upload_proofs,
+            can_run_proofs_by_authors,
         ])
 
         EditorialCollege.permissions.set([
@@ -340,14 +355,18 @@ class Command(BaseCommand):
 
         ProductionSupervisors.permissions.set([
             can_assign_production_officer,
+            can_take_decisions_related_to_proofs,
             can_view_all_production_streams,
+            can_run_proofs_by_authors,
             can_view_docs_scipost,
             can_view_production,
+            can_upload_proofs,
         ])
 
         ProductionOfficers.permissions.set([
             can_view_docs_scipost,
             can_view_production,
+            can_upload_proofs,
         ])
 
         PartnersAdmin.permissions.set([
diff --git a/scipost/static/scipost/assets/css/_alert.scss b/scipost/static/scipost/assets/css/_alert.scss
index 67ca539f4142e341a56a4f97b659e6e17216339a..6b7a7e21044e48ef372aaaaf4b47731ddcb3f9f8 100644
--- a/scipost/static/scipost/assets/css/_alert.scss
+++ b/scipost/static/scipost/assets/css/_alert.scss
@@ -1,5 +1,5 @@
 .alert {
-    padding: 0.75rem 1.25rem;
+    padding: 0.75rem 2.5rem 0.75rem 1.25rem;
     margin-bottom: 0.5rem;
     position: relative;
     clear: both;
@@ -16,7 +16,6 @@
     }
 }
 
-.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.
 .alert-dismissible {
     // Adjust close link position
     .close {
@@ -27,3 +26,15 @@
         cursor: pointer;
     }
 }
+
+
+@mixin scipost-alert-variant($background, $border, $color) {
+  background-color: $white;
+  border-top: 3px solid $color;
+}
+
+@each $color, $value in $theme-colors {
+  .alert-#{$color} {
+    @include scipost-alert-variant(theme-color-level($color, -10), theme-color-level($color, -9), theme-color-level($color, 6));
+  }
+}
diff --git a/scipost/static/scipost/assets/css/_list_group.scss b/scipost/static/scipost/assets/css/_list_group.scss
index 4755ec289813f41a3be705df5c68db980d203c26..7387c9c85f91dd41b8e5ec56003f3fc6bcb80099 100644
--- a/scipost/static/scipost/assets/css/_list_group.scss
+++ b/scipost/static/scipost/assets/css/_list_group.scss
@@ -23,3 +23,9 @@ ul.events-list {
         }
     }
 }
+
+ul[data-target="active-list"] {
+    li.active {
+        background-color: $gray-100;
+    }
+}
diff --git a/scipost/static/scipost/assets/css/_pool.scss b/scipost/static/scipost/assets/css/_pool.scss
index dea95e0ced53eeac71f9e825a6ba1c438a7d48eb..1b2ac64b648a293df03462ee9e84fbfc45e227bb 100644
--- a/scipost/static/scipost/assets/css/_pool.scss
+++ b/scipost/static/scipost/assets/css/_pool.scss
@@ -36,8 +36,4 @@ $pool-flex-width: calc(100% - 40px);
         padding: 5rem 0 3rem 0;
         text-align: center;
     }
-
-    li.active {
-        background-color: $gray-100;
-    }
 }
diff --git a/scipost/static/scipost/assets/js/scripts.js b/scipost/static/scipost/assets/js/scripts.js
index ce2d0a59dce94d80371442a40701bd14b61a4538..35883ec42ba87948933db9c2d3973914f7b41b2a 100644
--- a/scipost/static/scipost/assets/js/scripts.js
+++ b/scipost/static/scipost/assets/js/scripts.js
@@ -28,6 +28,7 @@ var getUrlParameter = function getUrlParameter(sParam) {
 };
 
 function init_page() {
+    console.log('init!')
     // Show right tab if url contains `tab` GET request
     var tab = getUrlParameter('tab')
     if (tab) {
@@ -39,6 +40,11 @@ function init_page() {
         $(this).parents('form').submit()
     });
 
+    // Start general toggle
+    $('[data-toggle="toggle"]').on('click', function() {
+        $($(this).attr('data-target')).toggle();
+    });
+
     activate_tooltip();
 }
 
@@ -46,11 +52,6 @@ $(function(){
     // Remove all alerts in screen automatically after 15sec.
     setTimeout(function() {hide_all_alerts()}, 15000);
 
-    // Start general toggle
-    $('[data-toggle="toggle"]').on('click', function() {
-        $($(this).attr('data-target')).toggle();
-    });
-
     // Change `tab` GET parameter for page-reload
     $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
         var tab_name = e.target.hash.substring(1)
@@ -71,13 +72,14 @@ $(function(){
 
         $.get(url + '?json=1').done(function(data) {
             // console.log('done', data);
-            $(target).html(data);
+            $(target).html(data).promise().done(function() {
+                init_page();
+            });
             $('[data-target="active-list"]')
                 .find('> li')
                 .removeClass('active')
             $(self).parents('[data-target="active-list"] > li')
                 .addClass('active');
-            init_page();
         });
     });
 });
diff --git a/partners/storage.py b/scipost/storage.py
similarity index 100%
rename from partners/storage.py
rename to scipost/storage.py
diff --git a/scipost/templates/widgets/checkbox_option_as_btn.html b/scipost/templates/widgets/checkbox_option_as_btn.html
index 191c44ea168836f19bac6b0fda9740e47dd1e0cc..708be219954434338b5ad880af819caf00f6cb5e 100644
--- a/scipost/templates/widgets/checkbox_option_as_btn.html
+++ b/scipost/templates/widgets/checkbox_option_as_btn.html
@@ -1,4 +1,3 @@
-
 {% include "django/forms/widgets/input.html" %}
 <label{% if widget.attrs.id %} class="btn btn-info" for="{{ widget.attrs.id }}"{% endif %}>
 {{ widget.label }}</label>
diff --git a/scipost/templatetags/bootstrap.py b/scipost/templatetags/bootstrap.py
index ef02a434f8fd4294f9157e329513d6f783608426..1b8b0d0d6ba28c47467ec45efd23a874cc440d32 100644
--- a/scipost/templatetags/bootstrap.py
+++ b/scipost/templatetags/bootstrap.py
@@ -11,7 +11,7 @@ register = template.Library()
 
 
 @register.filter
-def bootstrap(element, args='2,10'):
+def bootstrap(element, args='2,10', extra_classes=''):
     '''Pass arguments to tag by separating them using a comma ",".
 
     Arguments:
@@ -31,6 +31,9 @@ def bootstrap(element, args='2,10'):
         markup_classes['label'] += ' col-form-label-%s' % args.get(2)
         markup_classes['form_control'] = 'form-control-%s' % args.get(2)
 
+    if extra_classes:
+        markup_classes['extra'] = extra_classes
+
     return render(element, markup_classes)
 
 
@@ -50,6 +53,11 @@ def bootstrap_inline(element, args='2,10'):
     return render(element, markup_classes)
 
 
+@register.filter
+def bootstrap_grouped(element, args='2,10'):
+    return bootstrap(element, args, 'grouped')
+
+
 @register.filter
 def add_input_classes(field, extra_classes=''):
     if not is_checkbox(field) and not is_multiple_checkbox(field) \
diff --git a/scipost/views.py b/scipost/views.py
index b6c67e3297e4c11354988483ab9225e43acf08be..c142244dc7789f3c738d31341bfc9b0aaa3148ee 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -39,7 +39,7 @@ from commentaries.models import Commentary
 from comments.models import Comment
 from journals.models import Publication, Journal
 from news.models import NewsItem
-from submissions.models import Submission, EditorialAssignment, RefereeInvitation,\
+from submissions.models import Submission, RefereeInvitation,\
                                Report, EICRecommendation
 from partners.models import MembershipAgreement
 from theses.models import ThesisLink
diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html
index e234d4292d5778f38c6416c809a389752e35c321..1a0b85913247e8d3cc940466be9541f3b1893a4a 100644
--- a/submissions/templates/submissions/submission_detail.html
+++ b/submissions/templates/submissions/submission_detail.html
@@ -2,6 +2,7 @@
 
 {% load scipost_extras %}
 {% load submissions_extras %}
+{% load bootstrap %}
 
 {% block pagetitle %}: submission detail{% endblock pagetitle %}
 
@@ -116,6 +117,29 @@
     </div>
 {% endif %}
 
+{% if is_author or user|is_in_group:'Editorial Administrators' %}
+    {% if submission.production_stream.proofs.for_authors.exists %}
+        <div class="mb-4" id="proofsslist">
+            <h2>Proofs</h2>
+            <ul>
+                {% for proofs in submission.production_stream.proofs.for_authors %}
+                    <li>
+                        <a href="{{ proofs.get_absolute_url }}" target="_blank">Download version {{ proofs.version }}</a> &middot; uploaded: {{ proofs.created|date:"DATE_FORMAT" }} &middot;
+                        status: <span class="label label-secondary label-sm">{{ proofs.get_status_display }}</span>
+                        {% if proofs.status == 'accepted_sup' and proofs_decision_form and is_author %}
+                            <h3 class="mb-0 mt-2">Please advise the Production Team on your findings on Proofs version {{ proof.version }}</h3>
+                            <form method="post" action="{% url 'production:author_decision' proofs.slug %}" class="my-2">
+                                {% csrf_token %}
+                                {{ proofs_decision_form|bootstrap }}
+                                <input class="btn btn-primary btn-sm" type="submit" value="Submit">
+                            </form>
+                        {% endif %}
+                    </li>
+                {% endfor %}
+            </ul>
+        </div>
+    {% endif %}
+{% endif %}
 
 {% if user.is_authenticated and user|is_in_group:'Registered Contributors' %}
 <div class="row">
diff --git a/submissions/views.py b/submissions/views.py
index 0a6994baaaa9a703f5b217e64a5540a860868c66..b0fbd035edfcb72d960c8a2889e03ea2e9d41e1f 100644
--- a/submissions/views.py
+++ b/submissions/views.py
@@ -41,6 +41,7 @@ from scipost.utils import Utils
 from scipost.permissions import is_tester
 
 from comments.forms import CommentForm
+from production.forms import ProofsDecisionForm
 from production.models import ProductionStream
 
 import strings
@@ -177,6 +178,7 @@ def submission_detail_wo_vn_nr(request, arxiv_identifier_wo_vn_nr):
 
 def submission_detail(request, arxiv_identifier_w_vn_nr):
     submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr)
+    context = {}
     try:
         is_author = request.user.contributor in submission.authors.all()
         is_author_unchecked = (not is_author and
@@ -187,6 +189,8 @@ def submission_detail(request, arxiv_identifier_w_vn_nr):
                                           .get(author=request.user.contributor))
         except Report.DoesNotExist:
             unfinished_report_for_user = None
+
+        context['proofs_decision_form'] = ProofsDecisionForm()
     except AttributeError:
         is_author = False
         is_author_unchecked = False
@@ -209,16 +213,18 @@ def submission_detail(request, arxiv_identifier_w_vn_nr):
 
     recommendations = submission.eicrecommendations.all()
 
-    context = {'submission': submission,
-               'recommendations': recommendations,
-               'comments': comments,
-               'invited_reports': invited_reports,
-               'contributed_reports': contributed_reports,
-               'unfinished_report_for_user': unfinished_report_for_user,
-               'author_replies': author_replies,
-               'form': form,
-               'is_author': is_author,
-               'is_author_unchecked': is_author_unchecked}
+    context.update({
+        'submission': submission,
+        'recommendations': recommendations,
+        'comments': comments,
+        'invited_reports': invited_reports,
+        'contributed_reports': contributed_reports,
+        'unfinished_report_for_user': unfinished_report_for_user,
+        'author_replies': author_replies,
+        'form': form,
+        'is_author': is_author,
+        'is_author_unchecked': is_author_unchecked,
+    })
     return render(request, 'submissions/submission_detail.html', context)
 
 
diff --git a/templates/email/email_prospartner_contact.html b/templates/email/email_prospartner_contact.html
deleted file mode 100644
index 2d68fdbbb7aaaa87a717834e67702d76b8f3e5d8..0000000000000000000000000000000000000000
--- a/templates/email/email_prospartner_contact.html
+++ /dev/null
@@ -1,52 +0,0 @@
-{% load staticfiles %}
-{% if contact %}
-<p>Dear {{ contact.get_title_display }} {{ contact.last_name }},</p>
-{% else %}
-<p>Dear colleagues,</p>
-{% endif %}
-{% if message %}
-<p>
-  {{ message|linebreaks }}
-</p>
-{% endif %}
-{% if include_SPB_summary %}
-<p>
-  You might by now have heard of SciPost, a recently-launched initiative aiming to bring disruptive change to current academic publishing practices.
-</p>
-<p>
-  In summary, SciPost 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 on the <a href="https://scipost.org/about">about</a> and <a href="https://scipost.org/FAQ">FAQ</a> pages.
-</p>
-<p>
-  Crucially, as explained on our <a href="https://scipost.org/partners">Partners page</a>, SciPost follows a completely different funding model than traditional publishers, and provides a cost-slashing alternative to existing platforms. SciPost charges neither subscription fees, nor article processing charges; its activities are instead to be collectively financed through a Supporting Partners Board, formed by a worldwide consortium of institutions and organizations which directly or indirectly benefit from SciPost’s activities.
-</p>
-<p>
-  Support takes the form of a small financial commitment, collectively pooled to enable SciPost to perform all its publication-related activities, maintain its online portal and implement its long-term development plan.
-</p>
-<p>
-  In the <a href="https://scipost.org{% static 'scipost/SPB/SciPost_Supporting_Partner_Agreement.pdf' %}">agreement template</a>, you will find many more specific details about our operations, requirements and funding strategy. I would greatly appreciate if you took a few minutes to read through this document.
-</p>
-<p>
-  It would be a privilege to welcome you as members of our Supporting Partners Board. I am hereby contacting you to enquire whether your institution would consider joining. Your support at this time is crucially required to make our initiative sustainable, and to help make it possible for the community to reap all the benefits deriving form its viable implementation.
-</p>
-<p>
-I will be happy to provide any required further details. If you are interested, you can simply get in touch via this address (<a href="mailto:partners@scipost.org">partners@scipost.org</a>). I sincerely hope that SciPost will be able to count on your support.
-</p>
-<p>On behalf of the SciPost Foundation,</p>
-Prof. dr Jean-S&eacute;bastien Caux
-<br/>
-<br/><a href="mailto:J.S.Caux@uva.nl">J.S.Caux@uva.nl</a>
-<br/><a href="http://jscaux.org">jscaux.org</a>
-<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/>---------------------------------------------
-{% endif %}
diff --git a/templates/email/email_prospartner_contact.txt b/templates/email/email_prospartner_contact.txt
deleted file mode 100644
index 5f0455f8b1f0420e757364b23bc35ca5ac90a31c..0000000000000000000000000000000000000000
--- a/templates/email/email_prospartner_contact.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-{% if contact %}
-Dear {{ contact.get_title_display }} {{ contact.last_name }}, \n\n
-{% else %}
-Dear colleagues, \n\n
-{% endif %}
-{% if message %}{{ message }}{% endif %}
-{% if include_SPB_summary %}
-You might by now have heard of SciPost, a recently-launched initiative aiming to bring disruptive change to current academic publishing practices.
-\n\nIn summary, SciPost 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\nCrucially, as explained on our Partners page at https://scipost.org/partners, SciPost follows a completely different funding model than traditional publishers, and provides a cost-slashing alternative to existing platforms. SciPost charges neither subscription fees, nor article processing charges; its activities are instead to be collectively financed through a Supporting Partners Board, formed by a worldwide consortium of institutions and organizations which directly or indirectly benefit from SciPost’s activities.
-
-\n\nSupport takes the form of a small financial commitment, collectively pooled to enable SciPost to perform all its publication-related activities, maintain its online portal and implement its long-term development plan.
-
-\n\nIn the agreement template, which you can find online at https://scipost.org/static/scipost/SPB/SciPost_Supporting_Partner_Agreement.pdf, you will find many more specific details about our operations, requirements and funding strategy. I would greatly appreciate if you took a few minutes to read through this document.
-
-\n\nIt would be a privilege to welcome you as members of our Supporting Partners Board. I am hereby contacting you to enquire whether your institution would consider joining. Your support at this time is crucially required to make our initiative sustainable, and to help make it possible for the community to reap all the benefits deriving form its viable implementation.
-
-\n\nI will be happy to provide any required further details. If you are interested, you can simply get in touch via this address (partners@scipost.org). I sincerely hope that SciPost will be able to count on your support.
-
-\n\nOn behalf of the SciPost Foundation,
-\nProf. dr Jean-Sébastien Caux
-\n\nJ.S.Caux@uva.nl
-\nhttp://jscaux.org
-\n---------------------------------------------
-\nInstitute for Theoretical Physics\nUniversity of Amsterdam
-\nScience Park 904\n1098 XH Amsterdam\nThe Netherlands
-\n---------------------------------------------
-\ntel.: +31 (0)20 5255775\nfax: +31 (0)20 5255778
-\n---------------------------------------------
-
-{% endif %}