diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py
index 9c0d6f7b087fa982ce9cb92148465abc45d7c22f..98add02ea1bbf1165c0717c5c6e9d8bef02b4d9d 100644
--- a/SciPost_v1/settings/base.py
+++ b/SciPost_v1/settings/base.py
@@ -331,6 +331,10 @@ MAX_UPLOAD_SIZE = "2097152"  # Default max attachment size in Bytes; 2MB
 MEDIA_ROOT = 'local_files/media/'
 MEDIA_ROOT_SECURE = 'local_files/secure/media/'
 
+# Secure storage for apimail
+APIMAIL_MEDIA_ROOT_SECURE = 'local_files/secure/apimail/'
+APIMAIL_MEDIA_URL_SECURE = '/apimail/files/secure/'
+
 # Static files (CSS, JavaScript, Images)
 STATIC_URL = '/static/'
 STATIC_ROOT = 'local_files/static/'
@@ -489,7 +493,6 @@ ED_ASSIGMENT_DT_DELTA = timedelta(hours=6)
 
 
 # Mailgun credentials
-MAILGUN_DOMAIN_NAME = ''
 MAILGUN_API_KEY = ''
 
 # Pawning verification token
diff --git a/SciPost_v1/settings/local_JSC.py b/SciPost_v1/settings/local_JSC.py
index 03c966d65b8e5a36535260179180af6940ae08ba..98bf0c4dbbe35dc1258649fdaf1ed283104b43e3 100644
--- a/SciPost_v1/settings/local_JSC.py
+++ b/SciPost_v1/settings/local_JSC.py
@@ -34,7 +34,6 @@ CSP_REPORT_URI = get_secret('CSP_SENTRY')
 CSP_REPORT_ONLY = True
 
 # Mailgun credentials
-MAILGUN_DOMAIN_NAME = get_secret('MAILGUN_DOMAIN_NAME')
 MAILGUN_API_KEY = get_secret('MAILGUN_API_KEY')
 
 # CORS headers
diff --git a/SciPost_v1/settings/staging_do1.py b/SciPost_v1/settings/staging_do1.py
index 707705b3351097a504e0a5287815dd33e0a39615..b2812d090e6789b81ce8a67b8ff059cc7580388d 100644
--- a/SciPost_v1/settings/staging_do1.py
+++ b/SciPost_v1/settings/staging_do1.py
@@ -38,5 +38,4 @@ WSGI_APPLICATION = 'SciPost_v1.wsgi_staging_do1.application'
 #MONGO_DATABASE['port'] = get_secret('MONGO_DB_PORT')
 
 # Mailgun credentials
-MAILGUN_DOMAIN_NAME = get_secret('MAILGUN_DOMAIN_NAME')
 MAILGUN_API_KEY = get_secret('MAILGUN_API_KEY')
diff --git a/apimail/admin.py b/apimail/admin.py
index c9db40e95a0b6cd8c460d50c71f5adce6213e51a..6ea48c9d3e78691675e90ac96297cc8bfc46672b 100644
--- a/apimail/admin.py
+++ b/apimail/admin.py
@@ -5,6 +5,7 @@ __license__ = "AGPL v3"
 from django.contrib import admin
 
 from .models import (
+    Domain,
     EmailAccount, EmailAccountAccess,
     AttachmentFile,
     ComposedMessage, ComposedMessageAPIResponse,
@@ -13,6 +14,9 @@ from .models import (
     UserTag)
 
 
+admin.site.register(Domain)
+
+
 class EmailAccountAccessInline(admin.StackedInline):
     model = EmailAccountAccess
     extra = 0
diff --git a/apimail/api/serializers.py b/apimail/api/serializers.py
index c3c7a9cba87d2b96bc928b3ab0eae394459fbeab..115a3dc096bae8754dc4a510ecbe5e1102141ac8 100644
--- a/apimail/api/serializers.py
+++ b/apimail/api/serializers.py
@@ -118,7 +118,7 @@ class UserTagSerializer(serializers.ModelSerializer):
         fields = ['pk', 'user', 'label', 'unicode_symbol', 'variant']
 
     def get_queryset(self):
-        user = self.request.user
+        user = self.context['request'].user
         return UserTag.objects.filter(user=user)
 
 
@@ -126,11 +126,17 @@ class StoredMessageSerializer(serializers.ModelSerializer):
     attachment_files = AttachmentFileSerializer(many=True)
     event_set = EventSerializer(many=True)
     read = serializers.SerializerMethodField()
-    tags = UserTagSerializer(many=True)
+    tags = serializers.SerializerMethodField()
 
     def get_read(self, obj):
         return self.context['request'].user in obj.read_by.all()
 
+    def get_tags(self, obj):
+        return UserTagSerializer(
+            obj.tags.filter(user=self.context['request'].user),
+            many=True
+        ).data
+
     class Meta:
         model = StoredMessage
         fields = ['uuid', 'data', 'datetimestamp', 'attachment_files', 'event_set', 'read', 'tags']
diff --git a/apimail/api/views.py b/apimail/api/views.py
index 250465d09b99cc76e4caea7dab96df75b0371abc..6fa4bddd86caf1dc502ec4a87ddce09963f7b391 100644
--- a/apimail/api/views.py
+++ b/apimail/api/views.py
@@ -225,7 +225,6 @@ class StoredMessageUpdateReadAPIView(UpdateAPIView):
     queryset = StoredMessage.objects.all()
     serializer_class = StoredMessageSerializer
     lookup_field = 'uuid'
-    filter_backends = [StoredMessageFilterBackend,]
 
     def partial_update(self, request, *args, **kwargs):
         instance = self.get_object()
@@ -270,7 +269,7 @@ class StoredMessageUpdateTagAPIView(UpdateAPIView):
     Adds or removes a user tag on a StoredMessage.
     """
 
-    permission_classes = [IsAuthenticated, CanHandleStoredMessage]
+    permission_classes = [IsAuthenticated, CanReadStoredMessage]
     queryset = StoredMessage.objects.all()
     serializer_class = StoredMessageSerializer
     lookup_field = 'uuid'
diff --git a/apimail/management/commands/mailgun_get_events.py b/apimail/management/commands/mailgun_get_events.py
index da257277805684d39c62b4505371d720b64b8a41..9440010a0faada844bd6d28112540343743b1283 100644
--- a/apimail/management/commands/mailgun_get_events.py
+++ b/apimail/management/commands/mailgun_get_events.py
@@ -2,51 +2,81 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
+import time
 import requests
 
 from django.conf import settings
 from django.core.management import BaseCommand
 
 from ...exceptions import APIMailError
-from ...models import Event
+from ...models import Domain, Event
 
 
-def get_and_save_events(url=None):
+def get_and_save_events(url=None, domain_name=None, nr_minutes=2):
     """
-    Get events from Mailgun Events API.
+    For the given domain, get events from Mailgun Events API.
 
     This method treats a single page and saves new Events to the database.
     If no url is given, get the first page.
     Returns the paging JSON, if present, so traversing can be performed.
     """
-    response = requests.get(
-        url if url else "https://api.eu.mailgun.net/v3/%s/events" % settings.MAILGUN_DOMAIN_NAME,
-        auth=("api", settings.MAILGUN_API_KEY)
-    ).json()
-    events = response['items']
-    for item in events:
-        if not Event.objects.filter(data__timestamp=item['timestamp'],
-                                    data__id=item['id']).exists():
-            Event.objects.create(data=item)
-    info = {'nitems': len(events)}
-    if 'paging' in response:
-        info['paging'] = response['paging']
-    return info
+    response = {}
+    if url is None and domain_name is None:
+        raise APIMailError('Please provide either a url or domain_name to get_and_save_events.')
+    elif url:
+        response = requests.get(
+            url,
+            auth=("api", settings.MAILGUN_API_KEY)
+        ).json()
+    else:
+        print("Fetching items for the last %d minutes" % nr_minutes)
+        begin_time = int(time.time()) - 60*nr_minutes
+        response = requests.get(
+            "https://api.eu.mailgun.net/v3/%s/events" % domain_name,
+            auth=("api", settings.MAILGUN_API_KEY),
+            params={
+                "begin": begin_time,
+                "ascending": "yes"
+            }
+        ).json()
+        print(response)
+    try:
+        events = response['items']
+        print("Retrieved %d events" % len(response['items']))
+        for item in events:
+            if not Event.objects.filter(data__timestamp=item['timestamp'],
+                                        data__id=item['id']).exists():
+                Event.objects.create(data=item)
+        info = {'nitems': len(events)}
+        if 'paging' in response:
+            info['paging'] = response['paging']
+        return info
+    except KeyError:
+        print('No items found for domain %s\nresponse: %s' % (domain_name, response))
+    return {'nitems': 0}
 
 
 class Command(BaseCommand):
     """
     Perform a GET request to harvest Events from the Mailgun API, saving them to the DB.
     """
-
     help = 'Gets Events from the Mailgun Events API and saves them to the DB.'
 
+    def add_arguments(self, parser):
+        parser.add_argument(
+            '--nr_minutes', action='store', dest='nr_minutes', type=int,
+            help='number of minutes in the past where events are to be retrieved'
+        )
+
     def handle(self, *args, **kwargs):
-        info = get_and_save_events()
-        ctr = 1 # Safety: ensure no runaway requests
-        while ctr < 100 and info['nitems'] > 0:
-            info = get_and_save_events(url=info['paging']['next'])
-            ctr += 1
-            if ctr == 100:
-                raise APIMailError('Hard stop of mailgun_get_events: '
-                                   'harvested above 100 pages from Mailgun Events API')
+        nr_minutes = kwargs.get('nr_minutes', 2)
+        print("Getting events for the last %d minutes" % nr_minutes)
+        for domain in Domain.objects.active():
+            info = get_and_save_events(domain_name=domain.name, nr_minutes=nr_minutes)
+            ctr = 1 # Safety: ensure no runaway requests
+            while ctr < 100 and info['nitems'] > 0:
+                info = get_and_save_events(url=info['paging']['next'])
+                ctr += 1
+                if ctr == 100:
+                    raise APIMailError('Hard stop of mailgun_get_events: '
+                                       'harvested above 100 pages from Mailgun Events API')
diff --git a/apimail/management/commands/mailgun_get_stored_messages.py b/apimail/management/commands/mailgun_get_stored_messages.py
index 51078e22eb83d21f28c9fe0f9b764f09944d4c75..59427f612476707e75bcb2c3ac54d775ae06cc13 100644
--- a/apimail/management/commands/mailgun_get_stored_messages.py
+++ b/apimail/management/commands/mailgun_get_stored_messages.py
@@ -44,7 +44,6 @@ class Command(BaseCommand):
                 orphan.save()
 
             except StoredMessage.DoesNotExist:
-
                 # Need to get and create the message
                 try:
                     storage_url = orphan.data['storage']['url']
diff --git a/apimail/management/commands/mailgun_send_messages.py b/apimail/management/commands/mailgun_send_messages.py
index aa4589bfa955309a34141bf2b86c024a2f01b053..790fffcf65c261f0f5c9a336f9d03dda23edaa6f 100644
--- a/apimail/management/commands/mailgun_send_messages.py
+++ b/apimail/management/commands/mailgun_send_messages.py
@@ -32,7 +32,7 @@ class Command(BaseCommand):
                      for att in msg.attachment_files.all()]
 
             response = requests.post(
-                "https://api.eu.mailgun.net/v3/%s/messages" % settings.MAILGUN_DOMAIN_NAME,
+                "https://api.eu.mailgun.net/v3/%s/messages" % msg.from_account.domain.name,
                 auth=("api", settings.MAILGUN_API_KEY),
                 files=files,
                 data=data)
diff --git a/apimail/management/commands/mailgun_send_test_email.py b/apimail/management/commands/mailgun_send_test_email.py
index 76c40f2163a39bbc5de10ff0ede31a4939d56a2e..777c289146dc977b82d1a0af247b184255b6d1cd 100644
--- a/apimail/management/commands/mailgun_send_test_email.py
+++ b/apimail/management/commands/mailgun_send_test_email.py
@@ -7,24 +7,37 @@ import requests
 from django.conf import settings
 from django.core.management import BaseCommand
 
+from ...exceptions import APIMailError
+from ...models import Domain
+
 
 class Command(BaseCommand):
 
     def add_arguments(self, parser):
+        parser.add_argument(
+            '--from', type=str, required=True,
+            help='from address')
         parser.add_argument(
             '--to', type=str, required=True,
             help='to address')
 
     def handle(self, *args, **options):
-        data = {
-            'to': options.get('to'),
-            'from': 'techsupport@%s' % settings.MAILGUN_DOMAIN_NAME,
-            'subject': 'Test outgoing email',
-            'text': 'Testing outgoing email.'
-        }
-        response = requests.post(
-            "https://api.eu.mailgun.net/v3/%s/messages" % settings.MAILGUN_DOMAIN_NAME,
-            auth=("api", settings.MAILGUN_API_KEY),
-            data=data)
-        print(data)
-        print(response)
+        domain_name = options.get('from').rpartition('@')[2]
+        try:
+            Domain.objects.active().get(name=domain_name)
+            data = {
+                'from': options.get('from'),
+                'to': options.get('to'),
+                'subject': 'Test outgoing email',
+                'text': 'Testing outgoing email.'
+            }
+            response = requests.post(
+                "https://api.eu.mailgun.net/v3/%s/messages" % domain_name,
+                auth=("api", settings.MAILGUN_API_KEY),
+                data=data)
+            print(data)
+            print(response)
+        except Domain.MultipleObjectsReturned:
+            raise APIMailError('Multiple domains found in mailgun_send_test_email')
+        except Domain.DoesNotExist:
+            raise APIMailError('The sending domain was not recognized in mailgun_send_test_email')
diff --git a/apimail/managers.py b/apimail/managers.py
index f37512358243889b5e0b9a992ac4740d665663e0..c25d3c97fc3c197f5b94e0d7a94fe5f373285dae 100644
--- a/apimail/managers.py
+++ b/apimail/managers.py
@@ -7,6 +7,12 @@ import datetime
 from django.db import models
 
 
+class DomainQuerySet(models.QuerySet):
+    def active(self):
+        from apimail.models import Domain
+        return self.filter(status=Domain.STATUS_ACTIVE)
+
+
 class EmailAccountAccessQuerySet(models.QuerySet):
     def current(self):
         today = datetime.date.today()
diff --git a/apimail/migrations/0021_auto_20201017_1016.py b/apimail/migrations/0021_auto_20201017_1016.py
new file mode 100644
index 0000000000000000000000000000000000000000..01efa3ea1549c47d830d495cb25ac7be581989da
--- /dev/null
+++ b/apimail/migrations/0021_auto_20201017_1016.py
@@ -0,0 +1,30 @@
+# Generated by Django 2.2.16 on 2020-10-17 08:16
+
+import apimail.validators
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('apimail', '0020_auto_20200214_1159'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Domain',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100, unique=True, validators=[apimail.validators._simple_domain_name_validator])),
+            ],
+            options={
+                'ordering': ('name',),
+            },
+        ),
+        migrations.AddField(
+            model_name='emailaccount',
+            name='domain',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='email_accounts', to='apimail.Domain'),
+        ),
+    ]
diff --git a/apimail/migrations/0022_auto_20201017_1018.py b/apimail/migrations/0022_auto_20201017_1018.py
new file mode 100644
index 0000000000000000000000000000000000000000..3230748f828d7b533e809795d1f4515d7e3e0383
--- /dev/null
+++ b/apimail/migrations/0022_auto_20201017_1018.py
@@ -0,0 +1,19 @@
+# Generated by Django 2.2.16 on 2020-10-17 08:18
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('apimail', '0021_auto_20201017_1016'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='emailaccount',
+            name='domain',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='email_accounts', to='apimail.Domain'),
+        ),
+    ]
diff --git a/apimail/migrations/0023_domain_status.py b/apimail/migrations/0023_domain_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..615221464335fde1fa7824c86217f28ec28a8006
--- /dev/null
+++ b/apimail/migrations/0023_domain_status.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.16 on 2020-10-17 08:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('apimail', '0022_auto_20201017_1018'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='domain',
+            name='status',
+            field=models.CharField(choices=[('active', 'Active'), ('archived', 'Archived')], default='active', max_length=16),
+        ),
+    ]
diff --git a/apimail/migrations/0024_auto_20201017_1658.py b/apimail/migrations/0024_auto_20201017_1658.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d3f13b8a85b8091e5dd5ddb44a36cb5653929d6
--- /dev/null
+++ b/apimail/migrations/0024_auto_20201017_1658.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.2.16 on 2020-10-17 14:58
+
+import apimail.storage
+import apimail.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('apimail', '0023_domain_status'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='attachmentfile',
+            name='file',
+            field=models.FileField(storage=apimail.storage.APIMailSecureFileStorage(), upload_to='uploads/mail/attachments/%Y/%m/%d/', validators=[apimail.validators.validate_max_email_attachment_file_size]),
+        ),
+    ]
diff --git a/apimail/models/__init__.py b/apimail/models/__init__.py
index 4ef781dbb2148bb2a03bf4a46e32342c3422c54a..4f8395d53ee2ec0bc63bbef7012e0b5bfe279768 100644
--- a/apimail/models/__init__.py
+++ b/apimail/models/__init__.py
@@ -8,6 +8,8 @@ from .attachment import AttachmentFile
 
 from .composed_message import ComposedMessage, ComposedMessageAPIResponse
 
+from .domain import Domain
+
 from .event import Event
 
 from .stored_message import StoredMessage
diff --git a/apimail/models/account.py b/apimail/models/account.py
index c0537002de32642e3b76e77a7a4134fc05fa8a5d..fb59da3d1db1ef86471d81717800aad1dcf081f6 100644
--- a/apimail/models/account.py
+++ b/apimail/models/account.py
@@ -3,6 +3,7 @@ __license__ = "AGPL v3"
 
 
 from django.conf import settings
+from django.core.exceptions import ValidationError
 from django.db import models
 
 from ..managers import EmailAccountAccessQuerySet
@@ -14,6 +15,11 @@ class EmailAccount(models.Model):
 
     Access is specified on a per-user basis through the related EmailAccountAccess model.
     """
+    domain = models.ForeignKey(
+        'apimail.Domain',
+        related_name='email_accounts',
+        on_delete=models.CASCADE
+    )
     name = models.CharField(max_length=256)
     email = models.EmailField(unique=True)
     description = models.TextField()
@@ -24,6 +30,10 @@ class EmailAccount(models.Model):
     def __str__(self):
         return('%s <%s>' % (self.name, self.email))
 
+    def clean(self):
+        if self.email.rpartition('@')[2] != self.domain.name:
+            raise ValidationError("Email domain does not match domain name.")
+
 
 class EmailAccountAccess(models.Model):
     """
diff --git a/apimail/models/attachment.py b/apimail/models/attachment.py
index 891609ac5c509cc2758e2b0e7d76d6f682d39627..75ff1f3fc0af0e849e1754d0a9f399f4a4c9729d 100644
--- a/apimail/models/attachment.py
+++ b/apimail/models/attachment.py
@@ -8,7 +8,7 @@ from django.contrib.postgres.fields import JSONField
 from django.db import models
 from django.urls import reverse
 
-from scipost.storage import SecureFileStorage
+from ..storage import APIMailSecureFileStorage
 
 from ..validators import validate_max_email_attachment_file_size
 
@@ -27,7 +27,7 @@ class AttachmentFile(models.Model):
     file = models.FileField(
         upload_to='uploads/mail/attachments/%Y/%m/%d/',
         validators=[validate_max_email_attachment_file_size,],
-        storage=SecureFileStorage())
+        storage=APIMailSecureFileStorage())
 
     def __str__(self):
         return '%s (%s, %s)' % (self.data['name'], self.data['content-type'], self.file.size)
diff --git a/apimail/models/composed_message.py b/apimail/models/composed_message.py
index 82505f1482c8c31989a96302e83ea3480d61c550..491d6ea7a48b096501040f3221d6845123bc811b 100644
--- a/apimail/models/composed_message.py
+++ b/apimail/models/composed_message.py
@@ -10,10 +10,7 @@ from django.db import models
 from django.urls import reverse
 from django.utils import timezone
 
-from scipost.storage import SecureFileStorage
-
 from ..managers import ComposedMessageQuerySet
-from ..validators import validate_max_email_attachment_file_size
 
 
 class ComposedMessage(models.Model):
diff --git a/apimail/models/domain.py b/apimail/models/domain.py
new file mode 100644
index 0000000000000000000000000000000000000000..d278d8fc229f3af4d16eaff34cf672cdaed50707
--- /dev/null
+++ b/apimail/models/domain.py
@@ -0,0 +1,39 @@
+_copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django.db import models
+
+from ..managers import DomainQuerySet
+from ..validators import _simple_domain_name_validator
+
+
+class Domain(models.Model):
+    """
+    Domain name information.
+    """
+    STATUS_ACTIVE = 'active'
+    STATUS_ARCHIVED = 'archived'
+    STATUS_CHOICES = (
+        (STATUS_ACTIVE, 'Active'),
+        (STATUS_ARCHIVED, 'Archived')
+    )
+
+    name = models.CharField(
+        max_length=100,
+        validators=[_simple_domain_name_validator],
+        unique=True,
+    )
+    status = models.CharField(
+        max_length=16,
+        choices=STATUS_CHOICES,
+        default=STATUS_ACTIVE
+    )
+
+    objects = DomainQuerySet.as_manager()
+
+    class Meta:
+        ordering = ('name',)
+
+    def __str__(self):
+        return self.name
diff --git a/apimail/models/stored_message.py b/apimail/models/stored_message.py
index 1f4422e30231fc949439dc98ae638e27d72fba82..63f65740bde1f0b6b80cd055c899b6ea46f738b4 100644
--- a/apimail/models/stored_message.py
+++ b/apimail/models/stored_message.py
@@ -10,10 +10,7 @@ from django.db import models
 from django.urls import reverse
 from django.utils import timezone
 
-from scipost.storage import SecureFileStorage
-
 from ..managers import StoredMessageQuerySet
-from ..validators import validate_max_email_attachment_file_size
 
 
 class StoredMessage(models.Model):
diff --git a/apimail/permissions.py b/apimail/permissions.py
index e732cc28e65a88702f1197755411fbf04ac5e7eb..ef0d2a528a5665d15be934b39a53c592232417bb 100644
--- a/apimail/permissions.py
+++ b/apimail/permissions.py
@@ -33,10 +33,10 @@ class CanHandleStoredMessage(permissions.BasePermission):
         # Check, based on account accesses
         for access in request.user.email_account_accesses.filter(
                 rights=EmailAccountAccess.CRUD):
-            if ((access.account.email == obj.data.sender or
-                 access.account.email in obj.data.recipients)
-                and access.date_from < obj.datetimestamp
-                and access.data_until > obj.datetimestamp):
+            if ((access.account.email == obj.data['sender'] or
+                 access.account.email in obj.data['recipients'])
+                and access.date_from < obj.datetimestamp.date()
+                and access.date_until > obj.datetimestamp.date()):
                 return True
         return False
 
@@ -53,9 +53,9 @@ class CanReadStoredMessage(permissions.BasePermission):
 
         # Check, based on account accesses
         for access in request.user.email_account_accesses.all():
-            if ((access.account.email == obj.data.sender or
-                 access.account.email in obj.data.recipients)
-                and access.date_from < obj.datetimestamp
-                and access.data_until > obj.datetimestamp):
+            if ((access.account.email == obj.data['sender'] or
+                 access.account.email in obj.data['recipients'])
+                and access.date_from < obj.datetimestamp.date()
+                and access.date_until > obj.datetimestamp.date()):
                 return True
         return False
diff --git a/apimail/static/apimail/assets/vue/components/AttachmentListItem.vue b/apimail/static/apimail/assets/vue/components/AttachmentListItem.vue
index 5b5c86a9e4dcf8f68374337551ff8cf380009906..365d8bfd4a776fb9968beb6014e7edb36b280365 100644
--- a/apimail/static/apimail/assets/vue/components/AttachmentListItem.vue
+++ b/apimail/static/apimail/assets/vue/components/AttachmentListItem.vue
@@ -3,7 +3,9 @@
   <a :href="attachment.file" target="_blank">{{ attachment.data.name }}</a>
   &emsp;({{ content_type }}, {{ attachment.data.size }} bytes)
   <b-button class="bg-danger p-1" size="sm" @click.stop="$emit('remove')">
-    <i class="small fa fa-times text-white"></i>
+    <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x-circle-fill" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+      <path fill-rule="evenodd" d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
+    </svg>
   </b-button>
 </li>
 </template>
diff --git a/apimail/static/apimail/assets/vue/components/EmailListItem.vue b/apimail/static/apimail/assets/vue/components/EmailListItem.vue
index 0216e42e6b57a1f9ae14501c1a44d4d71f15b7d2..99be1e2fc09f5ab942719a9e483444598e71f81b 100644
--- a/apimail/static/apimail/assets/vue/components/EmailListItem.vue
+++ b/apimail/static/apimail/assets/vue/components/EmailListItem.vue
@@ -1,7 +1,11 @@
 <template>
   <li>
     {{ email }}&emsp;
-    <b-button class="bg-danger p-0" size="sm" @click.stop="$emit('remove')"><i class="small fa fa-times text-white"></i></b-button>
+    <b-button class="bg-danger p-0" size="sm" @click.stop="$emit('remove')">
+      <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x-circle-fill" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	<path fill-rule="evenodd" d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
+      </svg>
+    </b-button>
   </li>
 </template>
 
diff --git a/apimail/static/apimail/assets/vue/components/MessageComposer.vue b/apimail/static/apimail/assets/vue/components/MessageComposer.vue
index 159ea372ad0c4135c529b9cc207eecfba7564fa5..bf1442d5e30d8f95d4af4f9178bc29536054491e 100644
--- a/apimail/static/apimail/assets/vue/components/MessageComposer.vue
+++ b/apimail/static/apimail/assets/vue/components/MessageComposer.vue
@@ -88,35 +88,47 @@
           :pressed.sync="isActive.bold()"
           @click.stop.prevent="commands.bold"
           >
-	  <i class="fa fa-bold"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-type-bold" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path d="M8.21 13c2.106 0 3.412-1.087 3.412-2.823 0-1.306-.984-2.283-2.324-2.386v-.055a2.176 2.176 0 0 0 1.852-2.14c0-1.51-1.162-2.46-3.014-2.46H3.843V13H8.21zM5.908 4.674h1.696c.963 0 1.517.451 1.517 1.244 0 .834-.629 1.32-1.73 1.32H5.908V4.673zm0 6.788V8.598h1.73c1.217 0 1.88.492 1.88 1.415 0 .943-.643 1.449-1.832 1.449H5.907z"/>
+	  </svg>
 	</b-button>
         <b-button
 	  v-b-tooltip.hover title="italics"
           :pressed.sync="isActive.italic()"
           @click.stop.prevent="commands.italic"
           >
-	  <i class="fa fa-italic"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-type-italic" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path d="M7.991 11.674L9.53 4.455c.123-.595.246-.71 1.347-.807l.11-.52H7.211l-.11.52c1.06.096 1.128.212 1.005.807L6.57 11.674c-.123.595-.246.71-1.346.806l-.11.52h3.774l.11-.52c-1.06-.095-1.129-.211-1.006-.806z"/>
+	  </svg>
         </b-button>
         <b-button
 	  v-b-tooltip.hover title="strikethrough"
           :pressed.sync="isActive.strike()"
           @click.stop.prevent="commands.strike"
           >
-	  <i class="fa fa-strikethrough"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-type-strikethrough" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path d="M8.527 13.164c-2.153 0-3.589-1.107-3.705-2.81h1.23c.144 1.06 1.129 1.703 2.544 1.703 1.34 0 2.31-.705 2.31-1.675 0-.827-.547-1.374-1.914-1.675L8.046 8.5h3.45c.468.437.675.994.675 1.697 0 1.826-1.436 2.967-3.644 2.967zM6.602 6.5H5.167a2.776 2.776 0 0 1-.099-.76c0-1.627 1.436-2.768 3.48-2.768 1.969 0 3.39 1.175 3.445 2.85h-1.23c-.11-1.08-.964-1.743-2.25-1.743-1.23 0-2.18.602-2.18 1.607 0 .31.083.581.27.814z"/>
+	    <path fill-rule="evenodd" d="M15 8.5H1v-1h14v1z"/>
+	  </svg>
         </b-button>
         <b-button
 	  v-b-tooltip.hover title="underline"
           :pressed.sync="isActive.underline()"
           @click.stop.prevent="commands.underline"
           >
-	  <i class="fa fa-underline"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-type-underline" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path d="M5.313 3.136h-1.23V9.54c0 2.105 1.47 3.623 3.917 3.623s3.917-1.518 3.917-3.623V3.136h-1.23v6.323c0 1.49-.978 2.57-2.687 2.57-1.709 0-2.687-1.08-2.687-2.57V3.136z"/>
+	    <path fill-rule="evenodd" d="M12.5 15h-9v-1h9v1z"/>
+	  </svg>
         </b-button>
         <b-button
 	  v-b-tooltip.hover title="inline code"
           :pressed.sync="isActive.code()"
           @click.stop.prevent="commands.code"
           >
-          <i class="fa fa-code"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-code" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path fill-rule="evenodd" d="M5.854 4.146a.5.5 0 0 1 0 .708L2.707 8l3.147 3.146a.5.5 0 0 1-.708.708l-3.5-3.5a.5.5 0 0 1 0-.708l3.5-3.5a.5.5 0 0 1 .708 0zm4.292 0a.5.5 0 0 0 0 .708L13.293 8l-3.147 3.146a.5.5 0 0 0 .708.708l3.5-3.5a.5.5 0 0 0 0-.708l-3.5-3.5a.5.5 0 0 0-.708 0z"/>
+	  </svg>
         </b-button>
 	<b-button
           class="menubar__b-button"
@@ -124,7 +136,10 @@
           :pressed.sync="isActive.paragraph()"
           @click.stop.prevent="commands.paragraph"
           >
-          <i class="fa fa-paragraph"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-paragraph" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path fill-rule="evenodd" d="M8 1h4.5a.5.5 0 0 1 0 1H11v12.5a.5.5 0 0 1-1 0V2H9v12.5a.5.5 0 0 1-1 0V1z"/>
+	    <path d="M9 1v8H7a4 4 0 1 1 0-8h2z"/>
+	  </svg>
         </b-button>
         <b-button
 	  v-b-tooltip.hover title="level 1 heading"
@@ -152,28 +167,38 @@
           :pressed.sync="isActive.bullet_list()"
           @click.stop.prevent="commands.bullet_list"
           >
-          <i class="fa fa-list-ul"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-list-ul" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
+	  </svg>
         </b-button>
         <b-button
 	  v-b-tooltip.hover title="numbered list"
           :pressed.sync="isActive.ordered_list()"
           @click.stop.prevent="commands.ordered_list"
           >
-          <i class="fa fa-list-ol"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-list-ol" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5z"/>
+	    <path d="M1.713 11.865v-.474H2c.217 0 .363-.137.363-.317 0-.185-.158-.31-.361-.31-.223 0-.367.152-.373.31h-.59c.016-.467.373-.787.986-.787.588-.002.954.291.957.703a.595.595 0 0 1-.492.594v.033a.615.615 0 0 1 .569.631c.003.533-.502.8-1.051.8-.656 0-1-.37-1.008-.794h.582c.008.178.186.306.422.309.254 0 .424-.145.422-.35-.002-.195-.155-.348-.414-.348h-.3zm-.004-4.699h-.604v-.035c0-.408.295-.844.958-.844.583 0 .96.326.96.756 0 .389-.257.617-.476.848l-.537.572v.03h1.054V9H1.143v-.395l.957-.99c.138-.142.293-.304.293-.508 0-.18-.147-.32-.342-.32a.33.33 0 0 0-.342.338v.041zM2.564 5h-.635V2.924h-.031l-.598.42v-.567l.629-.443h.635V5z"/>
+	  </svg>
         </b-button>
         <b-button
 	  v-b-tooltip.hover title="blockquote"
           :pressed.sync="isActive.blockquote()"
           @click.stop.prevent="commands.blockquote"
           >
-          <i class="fa fa-quote-right"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-blockquote-left" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path fill-rule="evenodd" d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm5 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm-5 3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/>
+	    <path d="M3.734 6.352a6.586 6.586 0 0 0-.445.275 1.94 1.94 0 0 0-.346.299 1.38 1.38 0 0 0-.252.369c-.058.129-.1.295-.123.498h.282c.242 0 .431.06.568.182.14.117.21.29.21.521a.697.697 0 0 1-.187.463c-.12.14-.289.21-.503.21-.336 0-.577-.108-.721-.327C2.072 8.619 2 8.328 2 7.969c0-.254.055-.485.164-.692.11-.21.242-.398.398-.562.16-.168.33-.31.51-.428.18-.117.33-.213.451-.287l.211.352zm2.168 0a6.588 6.588 0 0 0-.445.275 1.94 1.94 0 0 0-.346.299c-.113.12-.199.246-.257.375a1.75 1.75 0 0 0-.118.492h.282c.242 0 .431.06.568.182.14.117.21.29.21.521a.697.697 0 0 1-.187.463c-.12.14-.289.21-.504.21-.335 0-.576-.108-.72-.327-.145-.223-.217-.514-.217-.873 0-.254.055-.485.164-.692.11-.21.242-.398.398-.562.16-.168.33-.31.51-.428.18-.117.33-.213.451-.287l.211.352z"/>
+	  </svg>
         </b-button>
         <b-button
 	  v-b-tooltip.hover title="code block"
           :pressed.sync="isActive.code_block()"
           @click.stop.prevent="commands.code_block"
           >
-          <i class="fa fa-code"></i> block
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-code" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path fill-rule="evenodd" d="M5.854 4.146a.5.5 0 0 1 0 .708L2.707 8l3.147 3.146a.5.5 0 0 1-.708.708l-3.5-3.5a.5.5 0 0 1 0-.708l3.5-3.5a.5.5 0 0 1 .708 0zm4.292 0a.5.5 0 0 0 0 .708L13.293 8l-3.147 3.146a.5.5 0 0 0 .708.708l3.5-3.5a.5.5 0 0 0 0-.708l-3.5-3.5a.5.5 0 0 0-.708 0z"/>
+	  </svg> block
         </b-button>
         <b-button
 	  v-b-tooltip.hover title="horizontal rule"
@@ -183,12 +208,18 @@
         <b-button
 	  v-b-tooltip.hover title="undo"
 	  @click.stop.prevent="commands.undo">
-          <i class="fa fa-undo"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrow-counterclockwise" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z"/>
+	    <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z"/>
+	  </svg>
         </b-button>
         <b-button
 	  v-b-tooltip.hover title="redo"
 	  @click.stop.prevent="commands.redo">
-          <i class="fa fa-repeat"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrow-clockwise" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
+	    <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
+	  </svg>
         </b-button>
       </div>
     </editor-menu-bar>
diff --git a/apimail/static/apimail/assets/vue/components/MessagesTable.vue b/apimail/static/apimail/assets/vue/components/MessagesTable.vue
index 95eb1b9041920fe53c311286d04860814b870120..e97dd857f0d72dc017e4c2be4a1c7f0c5e2597bd 100644
--- a/apimail/static/apimail/assets/vue/components/MessagesTable.vue
+++ b/apimail/static/apimail/assets/vue/components/MessagesTable.vue
@@ -1,7 +1,7 @@
 <template>
 <div>
 
-  <div v-if="accesses" class="m-2 mb-4">
+  <div v-if="currentSendingAccesses && currentSendingAccesses.length > 0" class="m-2 mb-4">
     <b-button
       v-b-modal.modal-newdraft
       variant="primary"
@@ -56,7 +56,7 @@
   </b-modal>
 
 
-  <div v-if="draftMessages.length > 0" class="m-2 mb-4">
+  <div v-if="draftMessages && draftMessages.length > 0" class="m-2 mb-4">
     <h2>Message drafts to complete</h2>
     <table class="table">
       <tr>
@@ -123,6 +123,19 @@
       <b-row>
 	<b-col class="col-lg-6">
 	  <h2>Messages for <strong>{{ accountSelected.email }}</strong></h2>
+	  <!-- <b-form-group -->
+	  <!--   label="Auto refresh (minutes): " -->
+	  <!--   label-cols-sm="4" -->
+	  <!--   label-align-sm="right" -->
+	  <!--   label-size="sm" -->
+	  <!--   > -->
+	  <!--   <b-form-radio-group -->
+	  <!--     v-model="refreshMinutes" -->
+	  <!--     :options="refreshMinutesOptions" -->
+	  <!--     class="float-center" -->
+	  <!--     > -->
+	  <!--   </b-form-radio-group> -->
+	  <!-- </b-form-group> -->
 	  <small class="p-2">Last loaded: {{ lastLoaded }}</small>
 	  <b-badge
 	    class="p-2"
@@ -130,7 +143,7 @@
 	    variant="primary"
 	    @click="refreshMessages"
 	    >
-	    Refresh
+	    Refresh now
 	  </b-badge>
 	</b-col>
 	<b-col class="col-lg-2">
@@ -311,10 +324,14 @@
       </template>
       <template v-slot:cell(actions)="row">
         <span v-if="row.detailsShowing">
-	  <i class="fa fa-angle-down"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-caret-down-fill" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path d="M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/>
+	  </svg>
 	</span>
 	<span v-else>
-	  <i class="fa fa-angle-right"></i>
+	  <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-caret-right-fill" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	    <path d="M12.14 8.753l-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
+	  </svg>
 	</span>
       </template>
       <template v-slot:row-details="row">
@@ -353,6 +370,7 @@ export default {
     data() {
 	return {
 	    accesses: null,
+	    currentSendingAccesses: null,
 	    accountSelected: null,
 	    draftMessages: [],
 	    draftMessageSelected: null,
@@ -387,6 +405,9 @@ export default {
 		{ text: 'read', value: true },
 		{ text: 'all', value: null },
 	    ],
+	    refreshInterval: null,
+	    refreshMinutes: 1,
+	    refreshMinutesOptions: [ 1, 5, 15, 60 ],
 	    tags: null,
 	    tagRequired: 'any',
 	}
@@ -398,6 +419,12 @@ export default {
 		.then(data => this.accesses = data.results)
 		.catch(error => console.error(error))
 	},
+	fetchCurrentSendingAccounts () {
+	    fetch('/mail/api/user_account_accesses?current=true&cansend=true')
+		.then(stream => stream.json())
+		.then(data => this.currentSendingAccesses = data.results)
+		.catch(error => console.error(error))
+	},
 	fetchTags () {
 	    fetch('/mail/api/user_tags')
 		.then(stream => stream.json())
@@ -493,6 +520,7 @@ export default {
     },
     mounted() {
 	this.fetchAccounts()
+	this.fetchCurrentSendingAccounts()
 	this.fetchTags()
 	this.fetchDrafts()
 	this.$root.$on('bv::modal::hide', (bvEvent, modalId) => {
@@ -503,7 +531,11 @@ export default {
 		this.fetchDrafts()
 	    }
 	})
+	// this.refreshInterval = setInterval(this.refreshMessages, this.refreshMinutes * 1000)
     },
+    // beforeDestroy() {
+    // 	clearInterval(this.refreshInterval)
+    // },
     watch: {
 	accountSelected: function () {
 	    this.$root.$emit('bv::refresh::table', 'my-table')
@@ -519,7 +551,11 @@ export default {
 	},
 	tagRequired: function () {
 	    this.$root.$emit('bv::refresh::table', 'my-table')
-	}
+	},
+	// refreshMinutes: function () {
+	//     clearInterval(this.refreshInterval)
+	//     this.refreshInterval = setInterval(this.refreshMessages, this.refreshMinutes * 1000)
+	// }
     }
 }
 
diff --git a/apimail/storage.py b/apimail/storage.py
new file mode 100644
index 0000000000000000000000000000000000000000..01ab996f3fdd2f74cfd38ec18b3844844accb6c0
--- /dev/null
+++ b/apimail/storage.py
@@ -0,0 +1,29 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django.conf import settings
+from django.core.files.storage import FileSystemStorage
+from django.utils.functional import cached_property
+
+
+class APIMailSecureFileStorage(FileSystemStorage):
+    """
+    Inherit default FileStorage system to prevent files from being publicly accessible
+    from a server location that is opened without this permission having been explicitly given.
+    """
+    @cached_property
+    def location(self):
+        """
+        This method determines the storage location for a new file. To secure the file from
+        public access, it is stored outside the default MEDIA_ROOT folder.
+
+        This also means you need to explicitly handle the file reading/opening!
+        """
+        if hasattr(settings, 'APIMAIL_MEDIA_ROOT_SECURE'):
+            return self._value_or_setting(self._location, settings.APIMAIL_MEDIA_ROOT_SECURE)
+        return super().location
+
+    @cached_property
+    def base_url(self):
+        return settings.APIMAIL_MEDIA_URL_SECURE
diff --git a/apimail/templates/apimail/message_list.html b/apimail/templates/apimail/message_list.html
index 01f15eeffe0a3d72634d36758871541f4d803702..51de5cb60f8d402d96d048f916927d4cf4c95040 100644
--- a/apimail/templates/apimail/message_list.html
+++ b/apimail/templates/apimail/message_list.html
@@ -4,6 +4,7 @@
 {% load static %}
 
 {% block headsup %}
+  {% render_bundle 'vendors~apimail' 'css' %}
   {% render_bundle 'apimail' 'css' %}
 {% endblock headsup %}
 
@@ -16,5 +17,6 @@
 {% endblock content %}
 
 {% block footer_script %}
+  {% render_bundle 'vendors~apimail' 'js' %}
   {% render_bundle 'apimail' 'js' %}
 {% endblock footer_script %}
diff --git a/apimail/validators.py b/apimail/validators.py
index 2c5581249aedefa5434ef6b9b0d4b3b1d616872b..8bd1602f02242c48f9f0d19f23a61fa60d619724 100644
--- a/apimail/validators.py
+++ b/apimail/validators.py
@@ -2,11 +2,27 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
+import string
+
 from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.template.defaultfilters import filesizeformat
 
 
+def _simple_domain_name_validator(value):
+    """
+    Validate that the given value contains no whitespaces to prevent common typos.
+
+    Taken from django.contrib.sites.models
+    """
+    checks = ((s in value) for s in string.whitespace)
+    if any(checks):
+        raise ValidationError(
+            "The domain name cannot contain any spaces or tabs.",
+            code='invalid',
+        )
+
+
 def validate_max_email_attachment_file_size(value):
     if value.size > int(settings.MAX_EMAIL_ATTACHMENT_FILE_SIZE):
         raise ValidationError(
diff --git a/scipost/static/scipost/assets/css/_pagination.scss b/scipost/static/scipost/assets/css/_pagination.scss
index b4345311d366c61e1ca4ead057df4d5c3ee871db..e4467f012490d64d0a4d7bda68532eff29544591 100644
--- a/scipost/static/scipost/assets/css/_pagination.scss
+++ b/scipost/static/scipost/assets/css/_pagination.scss
@@ -4,6 +4,10 @@
   @include border-radius();
 }
 
+.pagination li { // for DRF API pagination, file tepmlates/rest_framework/api.html
+    margin: 0.2rem;
+}
+
 .page-link {
   position: relative;
   display: block;
diff --git a/scipost/urls.py b/scipost/urls.py
index 3c7b093f18612d400a1dbeee8307a68a1c45b73c..7727ee33b944c1ebf41b102a0850af7943f1464a 100644
--- a/scipost/urls.py
+++ b/scipost/urls.py
@@ -32,7 +32,6 @@ app_name = 'scipost'
 
 urlpatterns = [
 
-
     # redirect for favicon
     re_path(r'^favicon\.ico$', favicon_view),
 
diff --git a/templates/rest_framework/api.html b/templates/rest_framework/api.html
index cda6f8219768ee9ae3abd6f4b831f7d52b3f3c77..260d689d178a867fd586c05b3295870428040537 100644
--- a/templates/rest_framework/api.html
+++ b/templates/rest_framework/api.html
@@ -37,11 +37,10 @@
       <title>SciPost API</title>
 
       {% block style %}
-	<link href="https://fonts.googleapis.com/css?family=Merriweather+Sans:300,400,700" rel="stylesheet">
-	<link rel="stylesheet" type="text/css" href="{% static 'scipost/SciPost.css' %}" />
-	<link rel="stylesheet" type="text/css" href="{% static 'fa/css/font-awesome.min.css' %}" />
 	<link rel="stylesheet" href="{% static 'flags/sprite-hq.css' %}">
 
+	{% render_bundle 'vendors~homepage~main~qr' %}
+	{% render_bundle 'vendors~main' %}
 	{% render_bundle 'main' %}
 
 	<link rel="shortcut icon" href="{% static 'scipost/images/scipost_favicon.png' %}"/>
@@ -85,82 +84,78 @@
             {% block content %}
 
               <div class="region"  aria-label="{% trans "request form" %}">
-		{% block request_forms %}
-
-		  {% if 'GET' in allowed_methods %}
-		    <form id="get-form" class="pull-right">
-		      <fieldset>
-			{% if api_settings.URL_FORMAT_OVERRIDE %}
-			  <div class="btn-group format-selection">
-			    <a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a>
-
-			    <button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request">
-			      <span class="caret"></span>
-			    </button>
-			    <ul class="dropdown-menu">
-			      {% for format in available_formats %}
-				<li>
-				  <a class="js-tooltip format-option" href="{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}" rel="nofollow" title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a>
-				</li>
-			      {% endfor %}
-			    </ul>
-			  </div>
-			{% else %}
+		{% if 'GET' in allowed_methods %}
+		  <form id="get-form" class="float-right">
+		    <fieldset>
+		      {% if api_settings.URL_FORMAT_OVERRIDE %}
+			<div class="btn-group format-selection">
 			  <a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a>
-			{% endif %}
-		      </fieldset>
-		    </form>
-		  {% endif %}
-
-		  {% if options_form %}
-		    <form class="button-form" action="{{ request.get_full_path }}" data-method="OPTIONS">
-		      <button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button>
-		    </form>
-		  {% endif %}
-
-		  {% if delete_form %}
-		    <button class="btn btn-danger button-form js-tooltip" title="Make a DELETE request on the {{ name }} resource" data-toggle="modal" data-target="#deleteModal">DELETE</button>
-
-		    <!-- Delete Modal -->
-		    <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
-		      <div class="modal-dialog">
-			<div class="modal-content">
-			  <div class="modal-body">
-			    <h4 class="text-center">Are you sure you want to delete this {{ name }}?</h4>
-			  </div>
-			  <div class="modal-footer">
-			    <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
-			    <form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE">
-			      <button class="btn btn-danger">Delete</button>
-			    </form>
-			  </div>
+
+			  <button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request">
+			    <span class="caret"></span>
+			  </button>
+			  <ul class="dropdown-menu">
+			    {% for format in available_formats %}
+			      <li>
+				<a class="js-tooltip format-option" href="{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}" rel="nofollow" title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a>
+			      </li>
+			    {% endfor %}
+			  </ul>
+			</div>
+		      {% else %}
+			<a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a>
+		      {% endif %}
+		    </fieldset>
+		  </form>
+		{% endif %}
+
+		{% if options_form %}
+		  <form class="button-form" action="{{ request.get_full_path }}" data-method="OPTIONS">
+		    <button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button>
+		  </form>
+		{% endif %}
+
+		{% if delete_form %}
+		  <button class="btn btn-danger button-form js-tooltip" title="Make a DELETE request on the {{ name }} resource" data-toggle="modal" data-target="#deleteModal">DELETE</button>
+
+		  <!-- Delete Modal -->
+		  <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+		    <div class="modal-dialog">
+		      <div class="modal-content">
+			<div class="modal-body">
+			  <h4 class="text-center">Are you sure you want to delete this {{ name }}?</h4>
+			</div>
+			<div class="modal-footer">
+			  <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+			  <form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE">
+			    <button class="btn btn-danger">Delete</button>
+			  </form>
 			</div>
 		      </div>
 		    </div>
-		  {% endif %}
-
-		  {% if extra_actions %}
-		    <div class="dropdown" style="float: right; margin-right: 10px">
-		      <button class="btn btn-default" id="extra-actions-menu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
-			{% trans "Extra Actions" %}
-			<span class="caret"></span>
-		      </button>
-		      <ul class="dropdown-menu" aria-labelledby="extra-actions-menu">
-			{% for action_name, url in extra_actions|items %}
-			  <li><a href="{{ url }}">{{ action_name }}</a></li>
-			{% endfor %}
-		      </ul>
-		    </div>
-		  {% endif %}
+		  </div>
+		{% endif %}
 
-		  {% if filter_form %}
-		    <button style="float: right; margin-right: 10px" data-toggle="modal" data-target="#filtersModal" class="btn btn-default">
-		      <span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
-		      {% trans "Filters" %}
+		{% if extra_actions %}
+		  <div class="dropdown" style="float: right; margin-right: 10px">
+		    <button class="btn btn-default" id="extra-actions-menu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+		      {% trans "Extra Actions" %}
+		      <span class="caret"></span>
 		    </button>
-		  {% endif %}
+		    <ul class="dropdown-menu" aria-labelledby="extra-actions-menu">
+		      {% for action_name, url in extra_actions|items %}
+			<li><a href="{{ url }}">{{ action_name }}</a></li>
+		      {% endfor %}
+		    </ul>
+		  </div>
+		{% endif %}
 
-		{% endblock request_forms %}
+		{% if filter_form %}
+		  <button style="float: right; margin-right: 10px" data-toggle="modal" data-target="#filtersModal" class="btn btn-default">
+		    <span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
+		    {% trans "Filters" %}
+		  </button>
+		{% endif %}
               </div>
 
               <div class="content-main" role="main"  aria-label="{% trans "main content" %}">
@@ -184,10 +179,7 @@
 		</div>
 
 		<div class="response-info" aria-label="{% trans "response info" %}">
-                  <pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% for key, val in response_headers|items %}
-		    <b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>{% endfor %}
-
-		  </span>{{ content|urlize_quoted_links }}</pre>
+                  <pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b><br>{% autoescape off %}{% for key, val in response_headers|items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span><br>{% endfor %}</span><br>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
 		</div>
               </div>
 
diff --git a/webpack.dev.config.js b/webpack.dev.config.js
index a65ccde50eb955cf57bc7fd487772c19d4819356..159747be7678925f89f37afb5fa3bb6138e1d4f4 100644
--- a/webpack.dev.config.js
+++ b/webpack.dev.config.js
@@ -20,6 +20,7 @@ module.exports = {
         ],
 	apimail: [
             "./apimail/static/apimail/assets/vue/messages_table.js",
+	],
 	qr: [
 	    "./scipost/static/scipost/assets/js/activate_qr.js",
 	],