diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py
index 1e99be8d609e9b9d7f2a7c9a82a403cd1f7497d7..2db7b8fce81c5183156da1e49600730beea7466b 100644
--- a/SciPost_v1/urls.py
+++ b/SciPost_v1/urls.py
@@ -55,6 +55,7 @@ urlpatterns = [
     url(r'^submission/', include('submissions.urls', namespace="_submissions")),
     url(r'^theses/', include('theses.urls', namespace="theses")),
     url(r'^thesis/', include('theses.urls', namespace="_theses")),
+    url(r'^mails/', include('mails.urls', namespace="mails")),
     url(r'^meetings/', include('virtualmeetings.urls', namespace="virtualmeetings")),
     url(r'^news/', include('news.urls', namespace="news")),
     url(r'^notifications/', include('notifications.urls', namespace="notifications")),
diff --git a/mails/backends/filebased.py b/mails/backends/filebased.py
index 3e1fad5a718eda2b05a22d9487656bcdcea4b181..30127a0cc677b76db8a54cedfeedb5d35b2d8ea0 100644
--- a/mails/backends/filebased.py
+++ b/mails/backends/filebased.py
@@ -51,8 +51,7 @@ class ModelEmailBackend(FileBackend):
 
         content_object = None
         mail_code = ''
-        if 'delayed_processing' in email_message.extra_headers \
-           and email_message.extra_headers['delayed_processing']:
+        if email_message.extra_headers.get('delayed_processing', False):
             status = 'not_rendered'
             content_object = email_message.extra_headers.get('content_object', None)
             mail_code = email_message.extra_headers.get('mail_code', '')
diff --git a/mails/core.py b/mails/core.py
index f6e1b0ebea69b2010ec824eb633092084cf86d43..d901dad943a65688c9dbc0adef34565e315ea902 100644
--- a/mails/core.py
+++ b/mails/core.py
@@ -1,6 +1,18 @@
 __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
+from html2text import HTML2Text
+import json
+import re
+import inspect
+
+from django.conf import settings
+from django.core.mail import EmailMultiAlternatives
+from django.db import models
+from django.template.loader import get_template
+
+from .exceptions import ConfigurationError
+
 
 class MailEngine:
     """
@@ -8,8 +20,14 @@ class MailEngine:
     the MailLog table.
     """
 
-    def __init__(self, mail_code, subject='', recipient_list=None, bcc=None, from_email='',
-            from_name=None, **kwargs):
+    _required_parameters = ['recipient_list', 'subject', 'from_email']
+    _possible_parameters = ['recipient_list', 'subject', 'from_email', 'from_name', 'bcc']
+    _email_fields = ['recipient_list', 'from_email', 'bcc']
+    _processed_template = False
+    _mail_sent = False
+
+    def __init__(self, mail_code, subject='', recipient_list=[], bcc=[], from_email='',
+            from_name='', context_object_name='', **kwargs):
         """
         Start engine with specific mail_code. Any other keyword argument that is passed will
         be used as a variable in the mail template.
@@ -27,25 +45,373 @@ class MailEngine:
         """
         self.mail_code = mail_code
         self.extra_config = {
-            'bcc': [],
+            'bcc': bcc,
             'subject': subject,
             'from_name': from_name,
-            'from_email': '',
-            'recipient_list': [],
+            'from_email': from_email,
+            'recipient_list': recipient_list,
+            'context_object_name': context_object_name,
         }
-        if from_email:
-            if not isinstance(from_email, str):
-                raise TypeError('"from_email" argument must be a string')
-            self.extra_config['from_email'] = from_email
-        if recipient_list:
-            if isinstance(recipient_list, str):
-                raise TypeError('"recipient_list" argument must be a list or tuple')
-            self.extra_config['recipient_list'] = list(recipient_list)
-        if bcc:
-            if isinstance(bcc, str):
-                raise TypeError('"bcc" argument must be a list or tuple')
-            self.extra_config['bcc'] = list(bcc)
+
+        # # Quick check given parameters
+        # if from_email:
+        #     if not isinstance(from_email, str):
+        #         raise TypeError('"from_email" argument must be a string')
+        # if recipient_list and not isinstance(recipient_list, list):
+        #     raise TypeError('"recipient_list" argument must be a list')
+        # if bcc and not isinstance(bcc, list):
+        #     raise TypeError('"bcc" argument must be a list')
         self.template_variables = kwargs
 
-    def start(self):
-        return True
+    def __repr__(self):
+        return '<%(cls)s code="%(code)s", validated=%(validated)s sent=%(sent)s>' % {
+            'cls': self.__class__.__name__,
+            'code': self.mail_code,
+            'validated': hasattr(self, 'mail_data'),
+            'sent': self._mail_sent,
+        }
+
+    def validate(self, render_template=False):
+        """Check if MailEngine is valid and ready for sending."""
+        self._read_configuration_file()
+        self._detect_and_save_object()
+        self._check_template_exists()
+        self._validate_configuration()
+        if render_template:
+            self.render_template()
+
+    def render_template(self, html_message=None):
+        """
+        Render the template associated with the mail_code. If html_message is given,
+        use this as a template instead.
+        """
+        if html_message:
+            self.mail_data['html_message'] = html_message
+        else:
+            mail_template = get_template('email/%s.html' % self.mail_code)
+            self.mail_data['html_message'] = mail_template.render(self.template_variables)  # Damn slow.
+
+        # Transform to non-HTML version.
+        handler = HTML2Text()
+        self.mail_data['message'] = handler.handle(self.mail_data['html_message'])
+        self._processed_template = True
+
+    def send_mail(self):
+        """Send the mail."""
+        if self._mail_sent:
+            # Prevent double sending when using a Django form.
+            return
+        elif not hasattr(self, 'mail_data'):
+            raise ValueError(
+                "The mail: %s could not be sent because the data didn't validate." % self.mail_code)
+
+        email = EmailMultiAlternatives(
+            self.mail_data['subject'],
+            self.mail_data['message'],
+            '%s <%s>' % (
+                self.mail_data.get('from_name', 'SciPost'),
+                self.mail_data.get('from_email', 'noreply@scipost.org')),
+            self.mail_data['recipient_list'],
+            bcc=self.mail_data['bcc'],
+            reply_to=[
+                self.mail_data.get('from_email', 'noreply@scipost.org')
+            ],
+            headers={
+                'delayed_processing': not self._processed_template,
+                'content_object': self.template_variables['object'],
+                'mail_code': self.mail_code,
+            })
+
+        # Send html version if available
+        if 'html_message' in self.mail_data:
+            email.attach_alternative(self.mail_data['html_message'], 'text/html')
+
+        email.send(fail_silently=False)
+        self._mail_sent = True
+
+        if self.template_variables['object'] and hasattr(self.template_variables['object'], 'mail_sent'):
+            self.instance.mail_sent()
+
+    def _detect_and_save_object(self):
+        """
+        Detect if less than or equal to one object exists and save it, else raise exception.
+        Stick to Django's convention of saving it as a central `object` variable.
+        """
+        object = None
+        context_object_name = None
+
+        if 'context_object_name' in self.mail_data:
+            context_object_name = self.mail_data['context_object_name']
+
+        if 'object' in self.template_variables:
+            object = self.template_variables['object']
+        elif 'instance' in self.template_variables:
+            object = self.template_variables['instance']
+        elif context_object_name and context_object_name in self.template_variables:
+            object = self.template_variables[context_object_name]
+        else:
+            for key, var in self.template_variables.items():
+                if isinstance(var, models.Model):
+                    if object:
+                        raise ValueError('Multiple db instances are given. Please specify which object to use.')
+                    else:
+                        object = var
+        self.template_variables['object'] = object
+
+        if not context_object_name and isinstance(object, models.Model):
+            context_object_name = self.template_variables['object']._meta.model_name
+
+        if context_object_name and object:
+            self.template_variables[context_object_name] = object
+
+
+    def _read_configuration_file(self):
+        """Retrieve default configuration for specific mail_code."""
+        json_location = '%s/templates/email/%s.json' % (settings.BASE_DIR, self.mail_code)
+
+        try:
+            self.mail_data = json.loads(open(json_location).read())
+        except OSError:
+            raise ImportError('No configuration file found. Mail code: %s' % self.mail_code)
+
+        # Check if configuration file is valid.
+        if 'subject' not in self.mail_data:
+            raise ConfigurationError('key "subject" is missing.')
+        if 'recipient_list' not in self.mail_data:
+            raise ConfigurationError('key "recipient_list" is missing.')
+
+        # Overwrite mail data if parameters are given.
+        for key, val in self.extra_config.items():
+            if val or key not in self.mail_data:
+                self.mail_data[key] = val
+
+    def _check_template_exists(self):
+        """Save template or raise TemplateDoesNotExist."""
+        self._template = get_template('email/%s.html' % self.mail_code)
+
+    def _validate_configuration(self):
+        """Check if all required data is given via either configuration or extra parameters."""
+
+        # Check data is complete
+        if not all(key in self.mail_data for key in self._required_parameters):
+            txt = 'Not all required parameters are given in the configuration file or on instantiation.'
+            txt += ' Check required parameters: {}'.format(self._required_parameters)
+            raise ConfigurationError(txt)
+
+        # Check all configuration value types
+        for email_key in ['subject', 'from_email', 'from_name']:
+            if email_key in self.mail_data and self.mail_data[email_key]:
+                if not isinstance(self.mail_data[email_key], str):
+                    raise ConfigurationError('"%(key)s" argument must be a string' % {
+                        'key': email_key,
+                    })
+        for email_key in ['recipient_list', 'bcc']:
+            if email_key in self.mail_data and self.mail_data[email_key]:
+                if not isinstance(self.mail_data[email_key], list):
+                    raise ConfigurationError('"%(key)s" argument must be a list' % {
+                        'key': email_key,
+                    })
+
+        # Validate all email addresses
+        for email_key in self._email_fields:
+            if email_key in self.mail_data:
+                if isinstance(self.mail_data[email_key], list):
+                    for i, email in enumerate(self.mail_data[email_key]):
+                        self.mail_data[email_key][i] = self._validate_email_addresses(email)
+                else:
+                    self.mail_data[email_key] = self._validate_email_addresses(self.mail_data[email_key])
+
+    def _validate_email_addresses(self, entry):
+        """Return email address given raw email or database relation given in `entry`."""
+        if re.match("[^@]+@[^@]+\.[^@]+", entry):
+            # Email string
+            return entry
+        elif self.template_variables['object']:
+            mail_to = self.template_variables['object']
+            for attr in entry.split('.'):
+                try:
+                    mail_to = getattr(mail_to, attr)
+                    if inspect.ismethod(mail_to):
+                        mail_to = mail_to()
+                except AttributeError:
+                    # Invalid property/mail
+                    raise KeyError('The property (%s) does not exist.' % entry)
+            return mail_to
+        raise KeyError('Neither an email adress nor db instance is given.')
+
+    # def pre_validation(self, *args, **kwargs):
+    #     """Validate the incoming data to initiate a specific mail."""
+    #     self.mail_code = kwargs.pop('mail_code')
+    #     self.instance = kwargs.pop('instance', None)
+    #     kwargs['object'] = self.instance  # Similar template nomenclature as Django.
+    #     self.mail_data = {
+    #         'subject': kwargs.pop('subject', ''),
+    #         'to_address': kwargs.pop('to', ''),
+    #         'bcc_to': kwargs.pop('bcc', ''),
+    #         'from_address_name': kwargs.pop('from_name', 'SciPost'),
+    #         'from_address': kwargs.pop('from', 'no-reply@scipost.org'),
+    #     }
+    #
+    #     # Gather meta data
+    #     json_location = '%s/templates/email/%s.json' % (settings.BASE_DIR, self.mail_code)
+    #
+        # try:
+        #     self.mail_data.update(json.loads(open(json_location).read()))
+        # except OSError:
+        #     if not self.mail_data['subject']:
+        #         raise NotImplementedError(('You did not create a valid .html and .json file '
+        #                                    'for mail_code: %s' % self.mail_code))
+    #
+    #     # Save central object/instance if not already
+    #     self.instance = self.get_object(**kwargs)
+    #
+    #     # Digest the templates
+        # if not self.delayed_processing:
+        #     mail_template = loader.get_template('email/%s.html' % self.mail_code)
+        #     if self.instance and self.mail_data.get('context_object'):
+        #         kwargs[self.mail_data['context_object']] = self.instance
+        #     self.mail_template = mail_template.render(kwargs)  # Damn slow.
+    #
+    #     # Gather Recipients data
+    #     try:
+    #         self.original_recipient = self._validate_single_entry(self.mail_data.get('to_address'))[0]
+    #     except IndexError:
+    #         self.original_recipient = ''
+    #
+    #     self.subject = self.mail_data['subject']
+    #
+    # def get_object(self, **kwargs):
+    #     if self.instance:
+    #         return self.instance
+    #
+    #     if self.mail_data.get('context_object'):
+    #         return kwargs.get(self.mail_data['context_object'], None)
+    #
+    # def _validate_single_entry(self, entry):
+    #     """
+    #     entry -- raw email string or path or properties leading to email mail field
+    #
+    #     Returns a list of email addresses found.
+    #     """
+    #         if entry and self.instance:
+    #         if re.match("[^@]+@[^@]+\.[^@]+", entry):
+    #             # Email string
+    #             return [entry]
+    #         else:
+                # mail_to = self.instance
+                # for attr in entry.split('.'):
+                #     try:
+                #         mail_to = getattr(mail_to, attr)
+                #         if inspect.ismethod(mail_to):
+                #             mail_to = mail_to()
+                #     except AttributeError:
+                #         # Invalid property/mail
+                #         return []
+                #
+                # if not isinstance(mail_to, list):
+                #     return [mail_to]
+                # else:
+                #     return mail_to
+    #     elif re.match("[^@]+@[^@]+\.[^@]+", entry):
+    #         return [entry]
+    #     else:
+    #         return []
+    # #
+    # def validate_bcc_list(self):
+    #     """
+    #     bcc_to in the .json file may contain multiple raw email addreses or property paths to
+    #     an email field. The different entries need to be comma separated.
+    #     """
+    #     # Get recipients list. Try to send through BCC to prevent privacy issues!
+    #     self.bcc_list = []
+    #     if self.mail_data.get('bcc_to'):
+    #         for bcc_entry in self.mail_data['bcc_to'].split(','):
+    #             self.bcc_list += self._validate_single_entry(bcc_entry)
+    #
+    # def validate_recipients(self):
+    #     # Check the send list
+    #     if isinstance(self.original_recipient, list):
+    #         recipients = self.original_recipient
+    #     elif not isinstance(self.original_recipient, str):
+    #         try:
+    #             recipients = list(self.original_recipient)
+    #         except TypeError:
+    #             recipients = [self.original_recipient]
+    #     else:
+    #         recipients = [self.original_recipient]
+    #     recipients = list(recipients)
+    #
+    #     # Check if email needs to be taken from an instance
+    #     _recipients = []
+    #     for recipient in recipients:
+    #         if isinstance(recipient, Contributor):
+    #             _recipients.append(recipient.user.email)
+    #         elif isinstance(recipient, get_user_model()):
+    #             _recipients.append(recipient.email)
+    #         elif isinstance(recipient, str):
+    #             _recipients.append(recipient)
+    #     self.recipients = _recipients
+    #
+    # def validate_message(self):
+        # if not self.html_message:
+        #     self.html_message = self.mail_template
+        # handler = HTML2Text()
+        # self.message = handler.handle(self.html_message)
+    #
+    # def validate(self):
+    #     """Execute different validation methods.
+    #
+    #     Only to be used when the default data is used, eg. not in the EmailTemplateForm.
+    #     """
+    #     self.validate_message()
+    #     self.validate_bcc_list()
+    #     self.validate_recipients()
+    #     self.save_mail_data()
+    #
+    # def save_mail_data(self):
+    #     """Save mail validated mail data; update default values of mail data."""
+    #     self.mail_data.update({
+    #         'subject': self.subject,
+    #         'message': self.message,
+    #         'html_message': self.html_message,
+    #         'recipients': self.recipients,
+    #         'bcc_list': self.bcc_list,
+    #     })
+    #
+    # def set_alternative_sender(self, from_name, from_address):
+    #     """TODO: REMOVE; DEPRECATED
+    #
+    #     Set an alternative from address/name from the default values received from the json
+    #     config file. The arguments only take raw string data, no methods/properties!
+    #     """
+    #     self.mail_data['from_address_name'] = from_name
+    #     self.mail_data['from_address'] = from_address
+    #
+    # def send(self):
+    #     """Send the mail assuming `mail_data` is validated and complete."""
+    #     if self.mail_sent:
+    #         # Prevent double sending when using a Django form.
+    #         return
+    #
+    #     email = EmailMultiAlternatives(
+    #         self.mail_data['subject'],
+    #         self.mail_data['message'],
+    #         '%s <%s>' % (self.mail_data['from_address_name'], self.mail_data['from_address']),
+    #         self.mail_data['recipients'],
+    #         bcc=self.mail_data['bcc_list'],
+    #         reply_to=[self.mail_data['from_address']],
+    #         headers={
+    #             'delayed_processing': self.delayed_processing,
+    #             'content_object': self.get_object(),
+    #             'mail_code': self.mail_code,
+    #         })
+    #
+    #     # Send html version if available
+    #     if 'html_message' in self.mail_data:
+    #         email.attach_alternative(self.mail_data['html_message'], 'text/html')
+    #
+    #     email.send(fail_silently=False)
+    #     self.mail_sent = True
+    #
+        # if self.instance and hasattr(self.instance, 'mail_sent'):
+        #     self.instance.mail_sent()
diff --git a/mails/exceptions.py b/mails/exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a765a71ac5ba34a6f3aa127adbbc1527f4c9a89
--- /dev/null
+++ b/mails/exceptions.py
@@ -0,0 +1,10 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+class ConfigurationError(Exception):
+    def __init__(self, name):
+        self.name = name
+
+    def __str__(self):
+        return 'Configuration error: {}'.format(self.name)
diff --git a/mails/forms.py b/mails/forms.py
index 472ae255b8594ec19449317df834a35ce395e4e8..5ff996511459f0e0e64dcbfe5894fceab5931c07 100644
--- a/mails/forms.py
+++ b/mails/forms.py
@@ -4,80 +4,150 @@ __license__ = "AGPL v3"
 
 from django import forms
 
-from .mixins import MailUtilsMixin
+from .core import MailEngine
 from .widgets import SummernoteEditor
 
 
-class EmailTemplateForm(forms.Form, MailUtilsMixin):
-    subject = forms.CharField(max_length=250, label="Subject*")
+class EmailForm(forms.Form):
+    """
+    This form is prefilled with data from a mail_code and is used by any user to send out
+    the mail after editing.
+    """
+
+    subject = forms.CharField(max_length=255, label="Subject*")
     text = forms.CharField(widget=SummernoteEditor, label="Text*")
-    extra_recipient = forms.EmailField(label="Optional: bcc this email to", required=False)
+    mail_field = forms.EmailField(label="Optional: bcc this email to", required=False)
     prefix = 'mail_form'
+    extra_config = {}
 
     def __init__(self, *args, **kwargs):
-        self.pre_validation(*args, **kwargs)
+        self.mail_code = kwargs.pop('mail_code')
+
+        # Check if all exta configurations are valid.
+        self.extra_config.update(kwargs.pop('mail_config', {}))
+
+        if not all(key in MailEngine._possible_parameters for key, val in self.extra_config.items()):
+            raise KeyError('Not all `extra_config` parameters are accepted.')
 
         # This form shouldn't be is_bound==True is there is any non-relavant POST data given.
-        data = args[0] if args else {}
-        if not data:
+        if len(args) > 0 and args[0]:
+            data = args[0]
+        elif 'data' in kwargs:
+            data = kwargs.pop('data')
+        else:
             data = {}
         if '%s-subject' % self.prefix in data.keys():
             data = {
                 '%s-subject' % self.prefix: data.get('%s-subject' % self.prefix),
                 '%s-text' % self.prefix: data.get('%s-text' % self.prefix),
-                '%s-extra_recipient' % self.prefix: data.get('%s-extra_recipient' % self.prefix),
-            }
-        elif kwargs.get('data', False):
-            data = {
-                '%s-subject' % self.prefix: kwargs['data'].get('%s-subject' % self.prefix),
-                '%s-text' % self.prefix: kwargs['data'].get('%s-text' % self.prefix),
-                '%s-extra_recipient' % self.prefix: kwargs['data'].get('%s-extra_recipient' % self.prefix),
+                '%s-mail_field' % self.prefix: data.get('%s-mail_field' % self.prefix),
             }
         else:
-            data = None
+            # Reset to prevent having a false-bound form.
+            data = {}
         super().__init__(data or None)
 
-        if not self.original_recipient:
-            self.fields['extra_recipient'].label = "Send this email to"
-            self.fields['extra_recipient'].required = True
-
         # Set the data as initials
-        self.fields['text'].initial = self.mail_template
-        self.fields['subject'].initial = self.mail_data['subject']
-
-    def save_data(self):
-        # Get text and html
-        self.html_message = self.cleaned_data['text']
-        self.subject = self.cleaned_data['subject']
-        self.validate_message()
-        self.validate_bcc_list()
-
-        # Get recipients list. Try to send through BCC to prevent privacy issues!
-        if self.cleaned_data.get('extra_recipient') and self.original_recipient:
-            self.bcc_list.append(self.cleaned_data.get('extra_recipient'))
-        elif self.cleaned_data.get('extra_recipient') and not self.original_recipient:
-            self.original_recipient = [self.cleaned_data.get('extra_recipient')]
-        elif not self.original_recipient:
-            self.add_error('extra_recipient', 'Please fill the bcc field to send the mail.')
-
-        self.validate_recipients()
-        self.save_mail_data()
-
-    def clean(self):
-        data = super().clean()
-        self.save_data()
-        return data
+        self.engine = MailEngine(self.mail_code, **self.extra_config, **kwargs)
+        self.engine.validate(render_template=True)
+        self.fields['text'].initial = self.engine.mail_data['html_message']
+        self.fields['subject'].initial = self.engine.mail_data['subject']
+
+        # if not self.original_recipient:
+        #     self.fields['mail_field'].label = "Send this email to"
+        #     self.fields['mail_field'].required = True
+
+    def is_valid(self):
+        """Fallback used in CBVs."""
+        if super().is_valid():
+            try:
+                self.engine.validate(render_template=False)
+                return True
+            except (ImportError, KeyError):
+                return False
+        return False
 
     def save(self):
-        """Because Django uses .save() by default..."""
-        self.send()
-        return self.instance
-
-
-
-class HiddenDataForm(forms.Form):
-    def __init__(self, form, *args, **kwargs):
-        super().__init__(form.data, *args, **kwargs)
-        for name, field in form.fields.items():
-            self.fields[name] = field
-            self.fields[name].widget = forms.HiddenInput()
+        self.engine.render_template(self.cleaned_data['text'])
+        self.engine.mail_data['subject'] = self.cleaned_data['subject']
+        if self.cleaned_data['mail_field']:
+            self.engine.mail_data['bcc'].append(self.cleaned_data['mail_field'])
+        self.engine.send_mail()
+        return self.engine.template_variables['object']
+
+
+class EmailTemplateForm(forms.Form):
+    """Deprecated."""
+    pass
+#     subject = forms.CharField(max_length=250, label="Subject*")
+#     text = forms.CharField(widget=SummernoteEditor, label="Text*")
+#     extra_recipient = forms.EmailField(label="Optional: bcc this email to", required=False)
+#     prefix = 'mail_form'
+#
+#     def __init__(self, *args, **kwargs):
+#         self.pre_validation(*args, **kwargs)
+#
+#         # This form shouldn't be is_bound==True is there is any non-relavant POST data given.
+#         data = args[0] if args else {}
+#         if not data:
+#             data = {}
+#         if '%s-subject' % self.prefix in data.keys():
+#             data = {
+#                 '%s-subject' % self.prefix: data.get('%s-subject' % self.prefix),
+#                 '%s-text' % self.prefix: data.get('%s-text' % self.prefix),
+#                 '%s-extra_recipient' % self.prefix: data.get('%s-extra_recipient' % self.prefix),
+#             }
+#         elif kwargs.get('data', False):
+#             data = {
+#                 '%s-subject' % self.prefix: kwargs['data'].get('%s-subject' % self.prefix),
+#                 '%s-text' % self.prefix: kwargs['data'].get('%s-text' % self.prefix),
+#                 '%s-extra_recipient' % self.prefix: kwargs['data'].get('%s-extra_recipient' % self.prefix),
+#             }
+#         else:
+#             data = None
+#         super().__init__(data or None)
+#
+#         if not self.original_recipient:
+#             self.fields['extra_recipient'].label = "Send this email to"
+#             self.fields['extra_recipient'].required = True
+#
+#         # Set the data as initials
+#         self.fields['text'].initial = self.mail_template
+#         self.fields['subject'].initial = self.mail_data['subject']
+#
+#     def save_data(self):
+#         # Get text and html
+#         self.html_message = self.cleaned_data['text']
+#         self.subject = self.cleaned_data['subject']
+#         self.validate_message()
+#         self.validate_bcc_list()
+#
+#         # Get recipients list. Try to send through BCC to prevent privacy issues!
+#         if self.cleaned_data.get('extra_recipient') and self.original_recipient:
+#             self.bcc_list.append(self.cleaned_data.get('extra_recipient'))
+#         elif self.cleaned_data.get('extra_recipient') and not self.original_recipient:
+#             self.original_recipient = [self.cleaned_data.get('extra_recipient')]
+#         elif not self.original_recipient:
+#             self.add_error('extra_recipient', 'Please fill the bcc field to send the mail.')
+#
+#         self.validate_recipients()
+#         self.save_mail_data()
+#
+#     def clean(self):
+#         data = super().clean()
+#         self.save_data()
+#         return data
+#
+#     def save(self):
+#         """Because Django uses .save() by default..."""
+#         self.send()
+#         return self.instance
+#
+#
+#
+# class HiddenDataForm(forms.Form):
+#     def __init__(self, form, *args, **kwargs):
+#         super().__init__(form.data, *args, **kwargs)
+#         for name, field in form.fields.items():
+#             self.fields[name] = field
+#             self.fields[name].widget = forms.HiddenInput()
diff --git a/mails/tests/test_core.py b/mails/tests/test_mail_code_1.py
similarity index 59%
rename from mails/tests/test_core.py
rename to mails/tests/test_mail_code_1.py
index cb8d9db80aff0d59e52aaae1c8de3b4ce3768348..3beaf7f8e451d8d4f1168b6e981a6a6cfb70e22a 100644
--- a/mails/tests/test_core.py
+++ b/mails/tests/test_mail_code_1.py
@@ -1,3 +1,9 @@
+# import json
+# from unittest.mock import patch, mock_open
+# import tempfile
+
+# from django.conf import settings
+from django.template.exceptions import TemplateDoesNotExist
 from django.test import TestCase
 
 from mails.core import MailEngine
@@ -7,18 +13,16 @@ class MailLogModelTests(TestCase):
     """
     Test the MailEngine object.
     """
-    # def setUp(self):
-    #     pass
 
-    def test_valid_initialisation(self):
-        """Test if the initialisation of the engine works properly."""
+    def test_valid_instantiation(self):
+        """Test if init method of the engine works properly."""
         # Test no mail_code given fails.
         with self.assertRaises(TypeError):
             MailEngine()
 
         # Test only mail_code given works.
         try:
-            MailEngine('test_mail_code_1')
+            MailEngine('tests/test_mail_code_1')
         except:
             # For whatever reason possible...
             self.fail('MailEngine() raised unexpectedly!')
@@ -26,7 +30,7 @@ class MailLogModelTests(TestCase):
         # Test all extra arguments are accepted.
         try:
             MailEngine(
-                'test_mail_code_1',
+                'tests/test_mail_code_1',
                 subject='Test subject A',
                 recipient_list=['test_A@example.org', 'test_B@example.org'],
                 bcc=['test_C@example.com', 'test_D@example.com'],
@@ -37,19 +41,29 @@ class MailLogModelTests(TestCase):
 
         # Test if only proper arguments are accepted.
         with self.assertRaises(TypeError):
-            MailEngine('test_mail_code_1', recipient_list='test_A@example.org')
+            MailEngine('tests/test_mail_code_1', recipient_list='test_A@example.org')
         with self.assertRaises(TypeError):
-            MailEngine('test_mail_code_1', bcc='test_A@example.org')
+            MailEngine('tests/test_mail_code_1', bcc='test_A@example.org')
         with self.assertRaises(TypeError):
-            MailEngine('test_mail_code_1', from_email=['test_A@example.org'])
+            MailEngine('tests/test_mail_code_1', from_email=['test_A@example.org'])
 
         # See if any other keyword argument is accepted and saved as template variable.
         try:
             engine = MailEngine(
-                'test_mail_code_1',
+                'tests/test_mail_code_1',
                 fake='Test subject A',
                 extra=['test_A@example.org'])
         except KeyError:
             self.fail('MailEngine() does not accept extra keyword arguments!')
+
         self.assertIs(engine.template_variables['fake'], 'Test subject A')
         self.assertListEqual(engine.template_variables['extra'], ['test_A@example.org'])
+
+    def test_invalid_mail_code(self):
+        """Test if invalid configuration files are handled properly."""
+        with self.assertRaises(ImportError):
+            MailEngine('tests/fake_mail_code_1')
+        with self.assertRaises(KeyError):
+            MailEngine('tests/test_mail_code_fault_1')
+        with self.assertRaises(TemplateDoesNotExist):
+            MailEngine('tests/test_mail_code_no_template_1')
diff --git a/mails/urls.py b/mails/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e9078b13dde633367f57076b7f6e7b10d357f8d
--- /dev/null
+++ b/mails/urls.py
@@ -0,0 +1,11 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+    url(r'^test/(?P<pk>\d+)/$', views.TestView.as_view(), name='test'),
+]
diff --git a/mails/utils.py b/mails/utils.py
index a1c49158e249ddb54c527e654e187c312b56d3a3..6fa1ad19a89ba9a9fb0946152455960467b0b700 100644
--- a/mails/utils.py
+++ b/mails/utils.py
@@ -2,18 +2,16 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
-from .mixins import MailUtilsMixin
+from .core import MailEngine
 
 
-class DirectMailUtil(MailUtilsMixin):
-    """
-    Same templates and json files as the form EmailTemplateForm, but this will directly send
-    the mails out, without intercepting and showing the mail editor to the user.
-    """
+class DirectMailUtil:
+    """Send a templated email directly; easiest possible way."""
 
-    def __init__(self, mail_code, *args, **kwargs):
-        kwargs['mail_code'] = mail_code
-        kwargs['instance'] = kwargs.pop('instance', None)
-        self.delayed_processing = kwargs.pop('delayed_processing', False)
-        super().__init__(*args, **kwargs)
-        self.validate()
+    def __init__(self, mail_code, delayed_processing=True, **kwargs):
+        # Set the data as initials
+        self.engine = MailEngine(mail_code, **kwargs)
+        self.engine.validate(render_template=not delayed_processing)
+
+    def send_mail(self):
+        return self.engine.send_mail()
diff --git a/mails/views.py b/mails/views.py
index 9aa031d86ea309074213fc11f82097f2d64aefc0..a004f6045c93df5895e3862622c262df5fd305ea 100644
--- a/mails/views.py
+++ b/mails/views.py
@@ -2,131 +2,202 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
-from django.contrib import messages
+# from django.contrib import messages
+# from django.db import models
+# from django.http import HttpRequest
 from django.shortcuts import render
 from django.views.generic.edit import UpdateView
 
-from .forms import EmailTemplateForm, HiddenDataForm
+from submissions.models import Submission
+from .forms import EmailForm
 
 
-class MailEditingSubView(object):
-    alternative_from_address = None  # Tuple: ('from_name', 'from_address')
+class MailView(UpdateView):
+    """Send a templated email after being edited by user."""
 
-    def __init__(self, request, mail_code, **kwargs):
-        self.request = request
-        self.context = kwargs.get('context', {})
-        self.template_name = kwargs.get('template', 'mails/mail_form.html')
-        self.header_template = kwargs.get('header_template', '')
-        self.mail_form = EmailTemplateForm(request.POST or None, mail_code=mail_code, **kwargs)
+    template_name = 'mails/mail_form.html'
+    form_class = EmailForm
+    mail_code = None
+    mail_config = {}
 
-    @property
-    def recipients_string(self):
-        return ', '.join(getattr(self.mail_form, 'mail_data', {}).get('recipients', ['']))
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+        kwargs['mail_code'] = self.mail_code
+        kwargs['mail_config'] = self.mail_config
+        return kwargs
 
-    def add_form(self, form):
-        self.context['transfer_data_form'] = HiddenDataForm(form)
+    # def form_invalid(self, form):
+    #     """If the form is valid, save the associated model."""
+    #     raise
+    #     self.object = form.save()
+    #     return super().form_valid(form)
 
-    def set_alternative_sender(self, from_name, from_address):
-        self.alternative_from_address = (from_name, from_address)
 
-    def is_valid(self):
-        return self.mail_form.is_valid()
 
-    def send(self):
-        if self.alternative_from_address:
-            self.mail_form.set_alternative_sender(
-                self.alternative_from_address[0], self.alternative_from_address[1])
-        return self.mail_form.send()
+class TestView(MailView):
+    """To be removed; exists for testing purposes only."""
+    mail_code = 'tests/test_mail_code_1'
+    model = Submission
+    success_url = '/'
 
-    def return_render(self):
-        self.context['form'] = self.mail_form
-        self.context['header_template'] = self.header_template
-        if hasattr(self.mail_form, 'instance') and self.mail_form.instance:
-            self.context['object'] = self.mail_form.instance
-        else:
-            self.context['object'] = None
-        return render(self.request, self.template_name, self.context)
 
-
-class MailEditorMixin:
+class MailEditorSubview:
     """
-    Use MailEditorMixin in edit CBVs to automatically implement the mail editor as
-    a post-form_valid hook.
+    This subview works as an interrupter for function based views.
 
-    The view must specify the `mail_code` variable.
+    If a FBV is completed, the MailEditingSubview will interrupt the request and
+    provide a form that give the user the possibility to edit a template based email before
+    sending it.
     """
-    object = None
-    mail_form = None
-    has_permission_to_send_mail = True
-    alternative_from_address = None  # Tuple: ('from_name', 'from_address')
 
-    def __init__(self, *args, **kwargs):
-        if not self.mail_code:
-            raise AttributeError(self.__class__.__name__ + ' object has no attribute `mail_code`')
-        super().__init__(*args, **kwargs)
+    template_name = 'mails/mail_form.html'
 
-    def get_template_names(self):
-        """
-        The mail editor form has its own template.
-        """
-        if self.mail_form and not self.mail_form.is_valid():
-            return ['mails/mail_form.html']
-        return super().get_template_names()
+    def __init__(self, request, mail_code, context=None, header_template=None, **kwargs):
+        self.mail_code = mail_code
+        self.context = context or {}
+        self.request = request
+        self.header_template = header_template
+        self.mail_form = EmailForm(request.POST or None, mail_code=mail_code, **kwargs)
+        self._is_valid = False
 
-    def post(self, request, *args, **kwargs):
+    def interrupt(self):
         """
-        Handle POST requests, but interpect the data if the mail form data isn't valid.
-        """
-        if not self.has_permission_to_send_mail:
-            # Don't use the mail form; don't send out the mail.
-            return super().post(request, *args, **kwargs)
-        self.object = self.get_object()
-        form = self.get_form()
-        if form.is_valid():
-            self.mail_form = EmailTemplateForm(request.POST or None, mail_code=self.mail_code,
-                                               instance=self.object)
-            if self.mail_form.is_valid():
-                return self.form_valid(form)
-
-            return self.render_to_response(
-                self.get_context_data(form=self.mail_form,
-                                      transfer_data_form=HiddenDataForm(form)))
-        else:
-            return self.form_invalid(form)
+        Interrupt request by rendering the templated email form.
 
-    def form_valid(self, form):
-        """
-        If both the regular form and mailing form are valid, save the form and run the mail form.
+        The `request` should be an HttpRequest instance that should be captured
+        and be included into the response of the interrupted response. Currently only
+        POST requests are supported.
         """
-        # Don't use the mail form; don't send out the mail.
-        if not self.has_permission_to_send_mail:
-            return super().form_valid(form)
-
-        if self.alternative_from_address:
-            # Set different from address if given.
-            self.mail_form.set_alternative_sender(
-                self.alternative_from_address[0], self.alternative_from_address[1])
-
-        response = super().form_valid(form)
-        try:
-            self.mail_form.send()
-        except AttributeError:
-            # self.mail_form is None
-            raise AttributeError('Did you check the order in which MailEditorMixin is used?')
-        messages.success(self.request, 'Mail sent')
-        return response
-
+        self.context['form'] = self.mail_form
+        self.context['header_template'] = self.header_template
+        if 'object' in self.mail_form.engine.template_variables:
+            self.context['object'] = self.mail_form.engine.template_variables['object']
+        else:
+            self.context['object'] = None
+        return render(self.request, self.template_name, self.context)
 
-class MailView(UpdateView):
-    template_name = 'mails/mail_form.html'
-    form_class = EmailTemplateForm
+    def is_valid(self):
+        """See if data is returned and valid."""
+        self._is_valid = self.mail_form.is_valid()
+        return self._is_valid
+
+    def send_mail(self):
+        """Send email as returned by user."""
+        if not self._is_valid:
+            raise ValueError(
+                "The mail: %s could not be sent because the data didn't validate." % self.mail_code)
+        return self.mail_form.save()
+
+
+class MailEditingSubView:
+    """Deprecated."""
+    pass
+#     alternative_from_address = None  # Tuple: ('from_name', 'from_address')
+#
+#     def __init__(self, request, mail_code, **kwargs):
+#         self.request = request
+#         self.context = kwargs.get('context', {})
+#         # self.template_name = kwargs.get('template', 'mails/mail_form.html')
+#         self.header_template = kwargs.get('header_template', '')
+#         self.mail_form = EmailForm(request.POST or None, mail_code=mail_code, **kwargs)
+#
+#     @property
+#     def recipients_string(self):
+#         return ', '.join(getattr(self.mail_form, 'mail_data', {}).get('recipients', ['']))
+#
+#     def add_form(self, form):
+#         """DEPRECATED"""
+#         self.context['transfer_data_form'] = HiddenDataForm(form)
+#
+#     def set_alternative_sender(self, from_name, from_address):
+#         """DEPRECATED"""
+#         self.alternative_from_address = (from_name, from_address)
+#
+#     def is_valid(self):
+#         return self.mail_form.is_valid()
+#
+#     def send(self):
+#         if self.alternative_from_address:
+#             self.mail_form.set_alternative_sender(
+#                 self.alternative_from_address[0], self.alternative_from_address[1])
+#         return self.mail_form.send()
+#
+#     def return_render(self):
+#         self.context['form'] = self.mail_form
+#         self.context['header_template'] = self.header_template
+#         if hasattr(self.mail_form, 'instance') and self.mail_form.instance:
+#             self.context['object'] = self.mail_form.instance
+#         else:
+#             self.context['object'] = None
+#         return render(self.request, self.template_name, self.context)
 
-    def get_form_kwargs(self):
-        kwargs = super().get_form_kwargs()
-        kwargs['mail_code'] = self.mail_code
-        return kwargs
 
-    def form_valid(self, form):
-        response = super().form_valid(form)
-        form.send()
-        return response
+class MailEditorMixin:
+    """Deprecated."""
+    pass
+    # """
+    # Use MailEditorMixin in edit CBVs to automatically implement the mail editor as
+    # a post-form_valid hook.
+    #
+    # The view must specify the `mail_code` variable.
+    # """
+    # object = None
+    # mail_form = None
+    # has_permission_to_send_mail = True
+    # alternative_from_address = None  # Tuple: ('from_name', 'from_address')
+    #
+    # def __init__(self, *args, **kwargs):
+    #     if not self.mail_code:
+    #         raise AttributeError(self.__class__.__name__ + ' object has no attribute `mail_code`')
+    #     super().__init__(*args, **kwargs)
+    #
+    # def get_template_names(self):
+    #     """
+    #     The mail editor form has its own template.
+    #     """
+    #     if self.mail_form and not self.mail_form.is_valid():
+    #         return ['mails/mail_form.html']
+    #     return super().get_template_names()
+    #
+    # def post(self, request, *args, **kwargs):
+    #     """
+    #     Handle POST requests, but interpect the data if the mail form data isn't valid.
+    #     """
+    #     if not self.has_permission_to_send_mail:
+    #         # Don't use the mail form; don't send out the mail.
+    #         return super().post(request, *args, **kwargs)
+    #     self.object = self.get_object()
+    #     form = self.get_form()
+    #     if form.is_valid():
+    #         self.mail_form = EmailForm(request.POST or None, mail_code=self.mail_code,
+    #                                            instance=self.object)
+    #         if self.mail_form.is_valid():
+    #             return self.form_valid(form)
+    #
+    #         return self.render_to_response(
+    #             self.get_context_data(form=self.mail_form,
+    #                                   transfer_data_form=HiddenDataForm(form)))
+    #     else:
+    #         return self.form_invalid(form)
+    #
+    # def form_valid(self, form):
+    #     """
+    #     If both the regular form and mailing form are valid, save the form and run the mail form.
+    #     """
+    #     # Don't use the mail form; don't send out the mail.
+    #     if not self.has_permission_to_send_mail:
+    #         return super().form_valid(form)
+    #
+    #     if self.alternative_from_address:
+    #         # Set different from address if given.
+    #         self.mail_form.set_alternative_sender(
+    #             self.alternative_from_address[0], self.alternative_from_address[1])
+    #
+    #     response = super().form_valid(form)
+    #     try:
+    #         self.mail_form.send()
+    #     except AttributeError:
+    #         # self.mail_form is None
+    #         raise AttributeError('Did you check the order in which MailEditorMixin is used?')
+    #     messages.success(self.request, 'Mail sent')
+    #     return response
diff --git a/submissions/views.py b/submissions/views.py
index d9d15c3301356488b2558e8df790e68da65c98da..c232d43e2dadd4342c059a0f374a55c55cd49f16 100644
--- a/submissions/views.py
+++ b/submissions/views.py
@@ -54,7 +54,7 @@ from invitations.constants import STATUS_SENT
 from invitations.models import RegistrationInvitation
 from journals.models import Journal
 from mails.utils import DirectMailUtil
-from mails.views import MailEditingSubView
+from mails.views import MailEditingSubView, MailEditorSubview
 from ontology.models import Topic
 from ontology.forms import SelectTopicForm
 from production.forms import ProofsDecisionForm
@@ -809,10 +809,10 @@ def assignment_failed(request, identifier_w_vn_nr):
     submission = get_object_or_404(Submission.objects.pool(request.user).unassigned(),
                                    preprint__identifier_w_vn_nr=identifier_w_vn_nr)
 
-    mail_request = MailEditingSubView(
+    mail_editor_view = MailEditorSubview(
         request, mail_code='submissions_assignment_failed', instance=submission,
         header_template='partials/submissions/admin/editorial_assignment_failed.html')
-    if mail_request.is_valid():
+    if mail_editor_view.is_valid():
         # Deprecate old Editorial Assignments
         EditorialAssignment.objects.filter(submission=submission).invited().update(
             status=STATUS_DEPRECATED)
@@ -826,10 +826,9 @@ def assignment_failed(request, identifier_w_vn_nr):
             request, 'Submission {arxiv} has failed pre-screening and been rejected.'.format(
                 arxiv=submission.preprint.identifier_w_vn_nr))
         messages.success(request, 'Authors have been informed by email.')
-        mail_request.send()
+        mail_editor_view.send_mail()
         return redirect(reverse('submissions:pool'))
-    else:
-        return mail_request.return_render()
+    return mail_editor_view.interrupt()
 
 
 @login_required
diff --git a/templates/email/submissions_assignment_failed.json b/templates/email/submissions_assignment_failed.json
index 9b1051a0bc9cdfdbf1e524a93326f6e2f45b55fd..8f6266bfddcf7d2d220c4464182689269f28fb12 100644
--- a/templates/email/submissions_assignment_failed.json
+++ b/templates/email/submissions_assignment_failed.json
@@ -1,8 +1,11 @@
 {
     "subject": "SciPost: pre-screening not passed",
-    "to_address": "submitted_by.user.email",
-    "bcc_to": "submissions@scipost.org",
-    "from_address_name": "SciPost Editorial Admin",
-    "from_address": "submissions@scipost.org",
-    "context_object": "submission"
+    "recipient_list": [
+        "submitted_by.user.email"
+    ],
+    "bcc": [
+        "submissions@scipost.org"
+    ],
+    "from_name": "SciPost Editorial Admin",
+    "from_email": "submissions@scipost.org"
 }
diff --git a/templates/email/tests/test_mail_code_1.html b/templates/email/tests/test_mail_code_1.html
new file mode 100644
index 0000000000000000000000000000000000000000..045ef365826e3319f8999c98141e722c2b8ab8e3
--- /dev/null
+++ b/templates/email/tests/test_mail_code_1.html
@@ -0,0 +1,10 @@
+<h3>Super test title</h3>
+<h4>Object: {{ object }}</h4>
+
+<p>
+    Dear reader,
+    <br>
+    <strong>Thanks for reading this test mail.</strong>
+    <br>
+    <em>¡Salud!</em>
+</p>
diff --git a/templates/email/tests/test_mail_code_1.json b/templates/email/tests/test_mail_code_1.json
new file mode 100644
index 0000000000000000000000000000000000000000..43cae8f8bdafe878a5aed5b91b5b247732e05871
--- /dev/null
+++ b/templates/email/tests/test_mail_code_1.json
@@ -0,0 +1,5 @@
+{
+    "subject": "SciPost Test",
+    "recipient_list": ["test@scipost.org"],
+    "from_email": "admin@scipost.org"
+}
diff --git a/templates/email/tests/test_mail_code_fault_1.json b/templates/email/tests/test_mail_code_fault_1.json
new file mode 100644
index 0000000000000000000000000000000000000000..48ed00ad03592db18fc74d2b1dc7fbec47d8850b
--- /dev/null
+++ b/templates/email/tests/test_mail_code_fault_1.json
@@ -0,0 +1,4 @@
+{
+    "subject": "SciPost Test",
+    "from_email": "admin@scipost.org"
+}
diff --git a/templates/email/tests/test_mail_code_no_template_1.json b/templates/email/tests/test_mail_code_no_template_1.json
new file mode 100644
index 0000000000000000000000000000000000000000..43cae8f8bdafe878a5aed5b91b5b247732e05871
--- /dev/null
+++ b/templates/email/tests/test_mail_code_no_template_1.json
@@ -0,0 +1,5 @@
+{
+    "subject": "SciPost Test",
+    "recipient_list": ["test@scipost.org"],
+    "from_email": "admin@scipost.org"
+}