diff --git a/apimail/admin.py b/apimail/admin.py
index 920e6fd680babfda9a3c74362544e9882a1d7d3f..c59fd81630f95d8e00dfc2d4f7adc8456593c9b0 100644
--- a/apimail/admin.py
+++ b/apimail/admin.py
@@ -6,6 +6,7 @@ from django.contrib import admin
 
 from .models import (
     EmailAccount, EmailAccountAccess,
+    ComposedMessage,
     Event,
     StoredMessage, StoredMessageAttachment,
     UserTag)
@@ -23,6 +24,12 @@ class EmailAccountAdmin(admin.ModelAdmin):
 admin.site.register(EmailAccount, EmailAccountAdmin)
 
 
+class ComposedMessageAdmin(admin.ModelAdmin):
+    pass
+
+admin.site.register(ComposedMessage, ComposedMessageAdmin)
+
+
 class EventAdmin(admin.ModelAdmin):
     pass
 
diff --git a/apimail/migrations/0012_composedmessage.py b/apimail/migrations/0012_composedmessage.py
new file mode 100644
index 0000000000000000000000000000000000000000..43486b0c778a9a06d5100d14f0fbea3300b43cdd
--- /dev/null
+++ b/apimail/migrations/0012_composedmessage.py
@@ -0,0 +1,36 @@
+# Generated by Django 2.1.8 on 2020-01-26 15:09
+
+from django.conf import settings
+import django.contrib.postgres.fields
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+import uuid
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('apimail', '0011_auto_20200118_0843'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ComposedMessage',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False)),
+                ('created_on', models.DateTimeField(default=django.utils.timezone.now)),
+                ('status', models.CharField(choices=[('draft', 'Draft'), ('ready', 'Ready for sending'), ('rendered', 'Rendered'), ('sent', 'Sent')], default='draft', max_length=8)),
+                ('to_recipient', models.EmailField(max_length=254)),
+                ('cc_recipients', django.contrib.postgres.fields.ArrayField(base_field=models.EmailField(max_length=254), blank=True, null=True, size=None)),
+                ('bcc_recipients', django.contrib.postgres.fields.ArrayField(base_field=models.EmailField(max_length=254), blank=True, null=True, size=None)),
+                ('subject', models.CharField(max_length=256)),
+                ('body_text', models.TextField()),
+                ('body_html', models.TextField(blank=True)),
+                ('author', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
+                ('from_account', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='apimail.EmailAccount')),
+            ],
+        ),
+    ]
diff --git a/apimail/models/__init__.py b/apimail/models/__init__.py
index 21b2984b3e39b6942a6b1a1b4f920ab0a6dff244..78b5e8e2161c0ce676c326013f317ee1f9d32678 100644
--- a/apimail/models/__init__.py
+++ b/apimail/models/__init__.py
@@ -4,6 +4,8 @@ __license__ = "AGPL v3"
 
 from .account import EmailAccount, EmailAccountAccess
 
+from .composed_message import ComposedMessage
+
 from .event import Event
 
 from .stored_message import StoredMessage, StoredMessageAttachment
diff --git a/apimail/models/composed_message.py b/apimail/models/composed_message.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b29877b92ced273679695861c8f1a65b516f9c6
--- /dev/null
+++ b/apimail/models/composed_message.py
@@ -0,0 +1,70 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+import uuid as uuid_lib
+
+from django.conf import settings
+from django.contrib.postgres.fields import ArrayField
+from django.db import models
+from django.utils import timezone
+
+
+class ComposedMessage(models.Model):
+    """
+    An outgoing email message.
+    """
+
+    STATUS_DRAFT = 'draft'
+    STATUS_READY = 'ready'
+    STATUS_RENDERED = 'rendered'
+    STATUS_SENT = 'sent'
+    STATUS_CHOICES = (
+        (STATUS_DRAFT, 'Draft'),
+        (STATUS_READY, 'Ready for sending'),
+        (STATUS_RENDERED, 'Rendered'),
+        (STATUS_SENT, 'Sent'),
+    )
+
+    uuid = models.UUIDField( # Used by the API to look up the record
+        db_index=True,
+        default=uuid_lib.uuid4,
+        editable=False)
+
+    author = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.PROTECT)
+
+    created_on = models.DateTimeField(default=timezone.now)
+
+    status = models.CharField(
+        max_length=8,
+        choices=STATUS_CHOICES,
+        default=STATUS_DRAFT)
+
+    from_account = models.ForeignKey(
+        'apimail.EmailAccount',
+        on_delete=models.PROTECT)
+
+    to_recipient = models.EmailField()
+
+    cc_recipients = ArrayField(
+        models.EmailField(),
+        blank=True, null=True)
+
+    bcc_recipients = ArrayField(
+        models.EmailField(),
+        blank=True, null=True)
+
+    subject = models.CharField(max_length=256)
+
+    body_text = models.TextField()
+    body_html = models.TextField(blank=True)
+
+    def __str__(self):
+        return '%s: %s (from %s to %s) [%s]' % (
+            self.created_on,
+            self.subject[:20],
+            self.from_account.email,
+            self.to_recipient,
+            self.get_status_display())