diff --git a/apimail/admin.py b/apimail/admin.py
index a9382eb59b750d961ad0b5a0af92b153822a01f5..c9db40e95a0b6cd8c460d50c71f5adce6213e51a 100644
--- a/apimail/admin.py
+++ b/apimail/admin.py
@@ -6,9 +6,10 @@ from django.contrib import admin
 
 from .models import (
     EmailAccount, EmailAccountAccess,
-    ComposedMessage, ComposedMessageAPIResponse, ComposedMessageAttachment,
+    AttachmentFile,
+    ComposedMessage, ComposedMessageAPIResponse,
     Event,
-    StoredMessage, StoredMessageAttachment,
+    StoredMessage,
     UserTag)
 
 
@@ -24,22 +25,23 @@ class EmailAccountAdmin(admin.ModelAdmin):
 admin.site.register(EmailAccount, EmailAccountAdmin)
 
 
-class ComposedMessageAPIResponseInline(admin.StackedInline):
-    model = ComposedMessageAPIResponse
+admin.site.register(AttachmentFile)
+
+
+class AttachmentFileInline(admin.StackedInline):
+    model = AttachmentFile
     extra = 0
     min_num = 0
 
 
-class ComposedMessageAttachmentInline(admin.StackedInline):
-    model = ComposedMessageAttachment
+class ComposedMessageAPIResponseInline(admin.StackedInline):
+    model = ComposedMessageAPIResponse
     extra = 0
     min_num = 0
 
 
 class ComposedMessageAdmin(admin.ModelAdmin):
-    inlines = [
-        ComposedMessageAttachmentInline,
-        ComposedMessageAPIResponseInline,]
+    inlines = [ComposedMessageAPIResponseInline,]
 
 admin.site.register(ComposedMessage, ComposedMessageAdmin)
 
@@ -50,14 +52,8 @@ class EventAdmin(admin.ModelAdmin):
 admin.site.register(Event, EventAdmin)
 
 
-class StoredMessageAttachmentInline(admin.StackedInline):
-    model = StoredMessageAttachment
-    extra = 0
-    min_num = 0
-
-
 class StoredMessageAdmin(admin.ModelAdmin):
-    inlines = [StoredMessageAttachmentInline,]
+    pass
 
 admin.site.register(StoredMessage, StoredMessageAdmin)
 
diff --git a/apimail/api/serializers.py b/apimail/api/serializers.py
index dda328b202072f4e25eb385c456c5d9f46a0befb..8984fdcbee806ed220ae9fe380c9937261e773c6 100644
--- a/apimail/api/serializers.py
+++ b/apimail/api/serializers.py
@@ -6,10 +6,11 @@ from django.urls import reverse
 from rest_framework import serializers
 
 from ..models import (
+    AttachmentFile,
     EmailAccount, EmailAccountAccess,
-    ComposedMessage, ComposedMessageAttachment,
+    ComposedMessage,
     Event,
-    StoredMessage, StoredMessageAttachment,
+    StoredMessage,
     UserTag)
 
 
@@ -29,41 +30,32 @@ class EmailAccountAccessSerializer(serializers.ModelSerializer):
         fields = ['account', 'rights', 'date_from', 'date_until']
 
 
-class ComposedMessageAttachmentSerializer(serializers.ModelSerializer):
-
-    class Meta:
-        model = ComposedMessageAttachment
-        fields = ['message', '_file']
-        read_only_fields = ['message']
-
-
-class ComposedMessageAttachmentLinkSerializer(serializers.ModelSerializer):
+class AttachmentFileSerializer(serializers.ModelSerializer):
     link = serializers.CharField(source='get_absolute_url', read_only=True)
 
     class Meta:
-        model = ComposedMessageAttachment
-        fields = ['message', '_file', 'link']
-        read_only_fields = ['message', '_file', 'link']
+        model = AttachmentFile
+        fields = ['uuid', 'data', 'file', 'link']
 
 
 class ComposedMessageSerializer(serializers.ModelSerializer):
-    attachments = ComposedMessageAttachmentLinkSerializer(many=True, read_only=True)
+    attachment_files = AttachmentFileSerializer(many=True, read_only=True)
 
     class Meta:
         model = ComposedMessage
         fields = ['uuid', 'author', 'created_on', 'status',
                   'from_account', 'to_recipient', 'cc_recipients', 'bcc_recipients',
                   'subject', 'body_text', 'body_html',
-                  'attachments'
+                  'attachment_files'
         ]
 
     def create(self, validated_data):
-        print("Here1")
+        # TODO
         cm = super().create(validated_data)
-        print("Here2")
         return cm
 
     def update(self, instance, validated_data):
+        # TODO
         cm = super().update(instance, validated_data)
         return cm
 
@@ -74,14 +66,6 @@ class EventSerializer(serializers.ModelSerializer):
         fields = ['uuid', 'data',]
 
 
-class StoredMessageAttachmentLinkSerializer(serializers.ModelSerializer):
-    link = serializers.CharField(source='get_absolute_url', read_only=True)
-
-    class Meta:
-        model = StoredMessageAttachment
-        fields = ['data', '_file', 'link']
-
-
 class UserTagSerializer(serializers.ModelSerializer):
     class Meta:
         model = UserTag
@@ -93,7 +77,7 @@ class UserTagSerializer(serializers.ModelSerializer):
 
 
 class StoredMessageSerializer(serializers.ModelSerializer):
-    attachments = StoredMessageAttachmentLinkSerializer(many=True)
+    attachment_files = AttachmentFileSerializer(many=True)
     event_set = EventSerializer(many=True)
     read = serializers.SerializerMethodField()
     tags = UserTagSerializer(many=True)
@@ -103,4 +87,4 @@ class StoredMessageSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = StoredMessage
-        fields = ['uuid', 'data', 'datetimestamp', 'attachments', 'event_set', 'read', 'tags']
+        fields = ['uuid', 'data', 'datetimestamp', 'attachment_files', 'event_set', 'read', 'tags']
diff --git a/apimail/api/views.py b/apimail/api/views.py
index 3f9348ce42310b3e79578d3b6d8425ef2b04340f..64e894c6ae2da78c4fbe8c18bbb8684763c1b6c3 100644
--- a/apimail/api/views.py
+++ b/apimail/api/views.py
@@ -17,7 +17,8 @@ from rest_framework.response import Response
 from rest_framework import filters, status
 
 from ..models import (
-    ComposedMessage, ComposedMessageAttachment,
+    AttachmentFile,
+    ComposedMessage,
     Event,
     StoredMessage, UserTag)
 
@@ -27,7 +28,8 @@ from ..permissions import (
 
 from .serializers import (
     EmailAccountSerializer, EmailAccountAccessSerializer,
-    ComposedMessageSerializer, ComposedMessageAttachmentSerializer,
+    AttachmentFileSerializer,
+    ComposedMessageSerializer,
     EventSerializer,
     StoredMessageSerializer,
     UserTagSerializer)
@@ -101,21 +103,12 @@ class ComposedMessageListAPIView(ListAPIView):
         return queryset
 
 
-class ComposedMessageAttachmentCreateView(CreateAPIView):
+class AttachmentFileCreateView(CreateAPIView):
     permission_classes = (IsAuthenticated,)
-    queryset = ComposedMessageAttachment.objects.all()
-    serializer_class = ComposedMessageAttachmentSerializer
+    queryset = AttachmentFile.objects.all()
+    serializer_class = AttachmentFileSerializer
     parser_classes = [FormParser, MultiPartParser,]
 
-    def create(self, request, *args, **kwargs):
-        data = request.data
-        data['message'] = None
-        serializer = self.get_serializer(data=data)
-        serializer.is_valid(raise_exception=True)
-        self.perform_create(serializer)
-        headers = self.get_success_headers(serializer.data)
-        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
-
 
 class EventListAPIView(ListAPIView):
     permission_classes = (IsAdminUser,)
diff --git a/apimail/management/commands/mailgun_get_stored_messages.py b/apimail/management/commands/mailgun_get_stored_messages.py
index 8a7337a5db33d741a7aa63454a5de80a140a8dcf..eb417c42074f29a462a448054498080a59cb419b 100644
--- a/apimail/management/commands/mailgun_get_stored_messages.py
+++ b/apimail/management/commands/mailgun_get_stored_messages.py
@@ -12,7 +12,10 @@ from django.core.files import File
 from django.core.management import BaseCommand
 
 from ...exceptions import APIMailError
-from ...models import Event, StoredMessage, StoredMessageAttachment
+from ...models import (
+    AttachmentFile,
+    Event,
+    StoredMessage)
 
 
 class Command(BaseCommand):
@@ -63,9 +66,9 @@ class Command(BaseCommand):
                         for chunk in r.iter_content(chunk_size=8192):
                             tf.write(chunk)
                         tf.seek(0)
-                        sma = StoredMessageAttachment.objects.create(
-                            message=sm, data=att_item)
-                        sma._file.save(att_item['name'], File(tf))
+                        af = AttachmentFile.objects.create(data=att_item)
+                        af.file.save(att_item['name'], File(tf))
+                        sm.attachment_files.add(af)
 
                 # Finally add a FK relation to any event associated to this new message
                 msgid = (sm.data['Message-Id'].lstrip('<')).rstrip('>')
diff --git a/apimail/migrations/0016_auto_20200206_0515.py b/apimail/migrations/0016_auto_20200206_0515.py
new file mode 100644
index 0000000000000000000000000000000000000000..46260da670d0e88fe7734bb976318fc985887d72
--- /dev/null
+++ b/apimail/migrations/0016_auto_20200206_0515.py
@@ -0,0 +1,53 @@
+# Generated by Django 2.1.8 on 2020-02-06 04:15
+
+import apimail.validators
+import django.contrib.postgres.fields.jsonb
+from django.db import migrations, models
+import django.db.models.deletion
+import scipost.storage
+import uuid
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('apimail', '0015_composedmessageattachment'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='AttachmentFile',
+            fields=[
+                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
+                ('file_upload', models.FileField(storage=scipost.storage.SecureFileStorage(), upload_to='uploads/mail/attachments/%Y/%m/%d/', validators=[apimail.validators.validate_max_email_attachment_file_size])),
+            ],
+        ),
+        migrations.CreateModel(
+            name='StoredMessageAttachmentFileBridge',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('data', django.contrib.postgres.fields.jsonb.JSONField(default=dict)),
+                ('attachment_file', models.ManyToManyField(to='apimail.AttachmentFile')),
+                ('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='apimail.StoredMessage')),
+            ],
+        ),
+        migrations.RemoveField(
+            model_name='composedmessageattachment',
+            name='message',
+        ),
+        migrations.RemoveField(
+            model_name='storedmessageattachment',
+            name='message',
+        ),
+        migrations.DeleteModel(
+            name='ComposedMessageAttachment',
+        ),
+        migrations.DeleteModel(
+            name='StoredMessageAttachment',
+        ),
+        migrations.AddField(
+            model_name='composedmessage',
+            name='attachments',
+            field=models.ManyToManyField(blank=True, to='apimail.AttachmentFile'),
+        ),
+    ]
diff --git a/apimail/migrations/0017_auto_20200206_0554.py b/apimail/migrations/0017_auto_20200206_0554.py
new file mode 100644
index 0000000000000000000000000000000000000000..b20a40f2e9677c0327fa254b6dd1aacdc71ded29
--- /dev/null
+++ b/apimail/migrations/0017_auto_20200206_0554.py
@@ -0,0 +1,39 @@
+# Generated by Django 2.1.8 on 2020-02-06 04:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('apimail', '0016_auto_20200206_0515'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='storedmessageattachmentfilebridge',
+            name='attachment_file',
+        ),
+        migrations.RemoveField(
+            model_name='storedmessageattachmentfilebridge',
+            name='message',
+        ),
+        migrations.RenameField(
+            model_name='attachmentfile',
+            old_name='file_upload',
+            new_name='file',
+        ),
+        migrations.RenameField(
+            model_name='composedmessage',
+            old_name='attachments',
+            new_name='attachment_files',
+        ),
+        migrations.AddField(
+            model_name='storedmessage',
+            name='attachment_files',
+            field=models.ManyToManyField(blank=True, to='apimail.AttachmentFile'),
+        ),
+        migrations.DeleteModel(
+            name='StoredMessageAttachmentFileBridge',
+        ),
+    ]
diff --git a/apimail/migrations/0018_attachmentfile_data.py b/apimail/migrations/0018_attachmentfile_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4f9311669083666d19cd14b0c8cb084e71ecdaa
--- /dev/null
+++ b/apimail/migrations/0018_attachmentfile_data.py
@@ -0,0 +1,19 @@
+# Generated by Django 2.1.8 on 2020-02-06 07:01
+
+import django.contrib.postgres.fields.jsonb
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('apimail', '0017_auto_20200206_0554'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='attachmentfile',
+            name='data',
+            field=django.contrib.postgres.fields.jsonb.JSONField(default=dict),
+        ),
+    ]
diff --git a/apimail/models/__init__.py b/apimail/models/__init__.py
index 07d51db2e362a0d1cbd0c651212d44a9c204c7a6..4ef781dbb2148bb2a03bf4a46e32342c3422c54a 100644
--- a/apimail/models/__init__.py
+++ b/apimail/models/__init__.py
@@ -4,10 +4,12 @@ __license__ = "AGPL v3"
 
 from .account import EmailAccount, EmailAccountAccess
 
-from .composed_message import ComposedMessage, ComposedMessageAPIResponse, ComposedMessageAttachment
+from .attachment import AttachmentFile
+
+from .composed_message import ComposedMessage, ComposedMessageAPIResponse
 
 from .event import Event
 
-from .stored_message import StoredMessage, StoredMessageAttachment
+from .stored_message import StoredMessage
 
 from .tag import UserTag
diff --git a/apimail/models/attachment.py b/apimail/models/attachment.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c3790365864ed8cfb3c25f30ec7d18a5aae56ee
--- /dev/null
+++ b/apimail/models/attachment.py
@@ -0,0 +1,34 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+import uuid as uuid_lib
+
+from django.contrib.postgres.fields import JSONField
+from django.db import models
+from django.urls import reverse
+
+from scipost.storage import SecureFileStorage
+
+from ..validators import validate_max_email_attachment_file_size
+
+
+class AttachmentFile(models.Model):
+    """
+    File representing an attachment to an email message.
+    """
+
+    uuid = models.UUIDField(
+        primary_key=True,
+        default=uuid_lib.uuid4,
+        unique=True,
+        editable=False)
+    data = JSONField(default=dict)
+    file = models.FileField(
+        upload_to='uploads/mail/attachments/%Y/%m/%d/',
+        validators=[validate_max_email_attachment_file_size,],
+        storage=SecureFileStorage())
+
+    def get_absolute_url(self):
+        return reverse('apimail:attachment_file',
+                       kwargs={'uuid': self.uuid})
diff --git a/apimail/models/composed_message.py b/apimail/models/composed_message.py
index 2e2e32a5fa5ae40cb5fd4e5279c15d29ad82d022..ee5d964cca617d06a313f213aa02f3da68a4fdc6 100644
--- a/apimail/models/composed_message.py
+++ b/apimail/models/composed_message.py
@@ -67,6 +67,10 @@ class ComposedMessage(models.Model):
     body_text = models.TextField()
     body_html = models.TextField(blank=True)
 
+    attachment_files = models.ManyToManyField(
+        'apimail.AttachmentFile',
+        blank=True)
+
     objects = ComposedMessageQuerySet.as_manager()
 
     def __str__(self):
@@ -92,19 +96,3 @@ class ComposedMessageAPIResponse(models.Model):
 
     class Meta:
         ordering = ['-datetime']
-
-
-class ComposedMessageAttachment(models.Model):
-    message = models.ForeignKey(
-        'apimail.ComposedMessage',
-        on_delete=models.CASCADE,
-        related_name='attachments'
-    )
-    _file = models.FileField(
-        upload_to='uploads/mail/composed_messages/attachments/%Y/%m/%d/',
-        validators=[validate_max_email_attachment_file_size,],
-        storage=SecureFileStorage())
-
-    def get_absolute_url(self):
-        return reverse('apimail:composed_message_attachment',
-                       kwargs={'uuid': self.message.uuid, 'pk': self.id})
diff --git a/apimail/models/stored_message.py b/apimail/models/stored_message.py
index 96da8db12eadc38312862929f57bd5106fc74e20..1f4422e30231fc949439dc98ae638e27d72fba82 100644
--- a/apimail/models/stored_message.py
+++ b/apimail/models/stored_message.py
@@ -34,6 +34,9 @@ class StoredMessage(models.Model):
         'apimail.UserTag',
         blank=True,
         related_name='messages')
+    attachment_files = models.ManyToManyField(
+        'apimail.AttachmentFile',
+        blank=True)
 
     objects = StoredMessageQuerySet.as_manager()
 
@@ -49,20 +52,3 @@ class StoredMessage(models.Model):
 
     def get_absolute_url_api(self):
         return reverse('apimail:api_stored_message_retrieve', kwargs={'uuid': self.uuid})
-
-
-class StoredMessageAttachment(models.Model):
-    message = models.ForeignKey(
-        'apimail.StoredMessage',
-        on_delete=models.CASCADE,
-        related_name='attachments' # doesn't collide with StoredMessage.data.attachments
-    )
-    data = JSONField(default=dict)
-    _file = models.FileField(
-        upload_to='uploads/mail/stored_messages/attachments/%Y/%m/%d/',
-        validators=[validate_max_email_attachment_file_size,],
-        storage=SecureFileStorage())
-
-    def get_absolute_url(self):
-        return reverse('apimail:message_attachment',
-                       kwargs={'uuid': self.message.uuid, 'pk': self.id})
diff --git a/apimail/static/apimail/assets/vue/components/MessageContent.vue b/apimail/static/apimail/assets/vue/components/MessageContent.vue
index 6823393f4dd59e62e61d84d23e86ffd9cebd957b..ae901160a572b9c85734d6891b381b6a6addf069 100644
--- a/apimail/static/apimail/assets/vue/components/MessageContent.vue
+++ b/apimail/static/apimail/assets/vue/components/MessageContent.vue
@@ -87,10 +87,10 @@
     </b-card-text>
     <template v-slot:footer>
       <div class="text-dark">
-	<div v-if="message.attachments">
+	<div v-if="message.attachment_files">
 	  <h3>Attachments:</h3>
 	  <ul>
-	    <li v-for="att in message.attachments">
+	    <li v-for="att in message.attachment_files">
 	      <a :href="att.link" target="_blank" class="text-primary">{{ att.data.name }}</a>
 	      &emsp;{{ att.data["content-type"] }}&nbsp;({{ att.data.size }} b)
 	    </li>
diff --git a/apimail/urls.py b/apimail/urls.py
index cfb68c000669f4c6553f85ce5d3e0852bb9f65ac..4047c664ee23ab1abe66ce03ec219fd43175210a 100644
--- a/apimail/urls.py
+++ b/apimail/urls.py
@@ -92,9 +92,9 @@ urlpatterns = [
         TemplateView.as_view(template_name='apimail/message_list.html'),
         name='message_list'
     ),
-    path( # /mail/message/<uuid>/attachments/<int>
-        'message/<uuid:uuid>/attachments/<int:pk>',
+    path( # /mail/attachment_file/<uuid>
+        'attachment_file/<uuid:uuid>',
         views.attachment_file,
-        name='message_attachment'
+        name='attachment_file'
     ),
 ]
diff --git a/apimail/views.py b/apimail/views.py
index 67825ad1eddc33b34529094aa2de6194c048172b..cd54b1cb906b17da5aeca84bdeadf134f17cc4ff 100644
--- a/apimail/views.py
+++ b/apimail/views.py
@@ -7,23 +7,18 @@ import mimetypes
 from django.http import HttpResponse
 from django.shortcuts import get_object_or_404
 
-from .models import ComposedMessageAttachment, StoredMessageAttachment
+from .models import AttachmentFile
 
 
-def attachment_file(request, uuid, pk):
+def attachment_file(request, uuid):
     """
-    Return an attachment to either a Composed Message or a StoredMessage.
+    Return an attachment file.
     """
-    try:
-        att = ComposedMessageAttachment.objects.get(
-            message__uuid=uuid, id=pk)
-    except ComposedMessageAttachment.DoesNotExist:
-        att = get_object_or_404(StoredMessageAttachment,
-                                message__uuid=uuid, id=pk)
-    content_type, encoding = mimetypes.guess_type(att._file.path)
+    att = get_object_or_404(AttachmentFile, uuid=uuid)
+    content_type, encoding = mimetypes.guess_type(att.attachment_file.path)
     content_type = content_type or 'application/octet-stream'
-    response = HttpResponse(att._file.read(), content_type=content_type)
+    response = HttpResponse(att.attachment_file.read(), content_type=content_type)
     response['Content-Disposition'] = (
-        'filename=%s' % att._file.name.rpartition('/')[2])
+        'filename=%s' % att.attachment_file.name.rpartition('/')[2])
     response["Content-Encoding"] = encoding
     return response