diff --git a/news/admin.py b/news/admin.py
index 2d5777a2aea2ac99d90e58d872fc73833d3a516b..c46a0edb5ae5e22f5dcd59dc72d29c635814d3d7 100644
--- a/news/admin.py
+++ b/news/admin.py
@@ -4,7 +4,18 @@ __license__ = "AGPL v3"
 
 from django.contrib import admin
 
-from .models import NewsItem
+from .models import NewsLetter, NewsItem, NewsLetterNewsItemsTable
+
+
+class NewsLetterNewsItemsTableInline(admin.TabularInline):
+    model = NewsLetterNewsItemsTable
+
+class NewsLetterAdmin(admin.ModelAdmin):
+    search_fields = ['intro', 'closing']
+    list_display = ['__str__', 'published']
+    inlines = [NewsLetterNewsItemsTableInline]
+
+admin.site.register(NewsLetter, NewsLetterAdmin)
 
 
 class NewsItemAdmin(admin.ModelAdmin):
diff --git a/news/forms.py b/news/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..843a31a652217ae7a9d7d87e61a1f862f681a0ff
--- /dev/null
+++ b/news/forms.py
@@ -0,0 +1,31 @@
+__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django import forms
+
+from .models import NewsLetter, NewsItem, NewsLetterNewsItemsTable
+
+
+class NewsLetterForm(forms.ModelForm):
+
+    class Meta:
+        model = NewsLetter
+        fields = ['date', 'intro', 'closing', 'published']
+
+
+class NewsItemForm(forms.ModelForm):
+
+    class Meta:
+        model = NewsItem
+        fields = ['date', 'headline', 'blurb_short', 'blurb',
+                  'image', 'css_class',
+                  'followup_link', 'followup_link_text',
+                  'published', 'on_homepage']
+
+
+class NewsLetterNewsItemsTableForm(forms.ModelForm):
+
+    class Meta:
+        model = NewsLetterNewsItemsTable
+        fields = ['newsitem']
diff --git a/news/migrations/0002_auto_20180706_1459.py b/news/migrations/0002_auto_20180706_1459.py
new file mode 100644
index 0000000000000000000000000000000000000000..d06e2f8426df6d1c31288f15a2fb44c964ed647a
--- /dev/null
+++ b/news/migrations/0002_auto_20180706_1459.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-07-06 12:59
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('news', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='NewsLetter',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField()),
+                ('intro', models.TextField()),
+                ('closing', models.TextField()),
+                ('published', models.BooleanField(default=False)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='NewsLetterNewsItemsTable',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('order', models.PositiveSmallIntegerField()),
+            ],
+        ),
+        migrations.AddField(
+            model_name='newsitem',
+            name='blurb_short',
+            field=models.TextField(default='', help_text='Short version for use in Newsletter/emails etc'),
+        ),
+        migrations.AddField(
+            model_name='newsitem',
+            name='css_class',
+            field=models.CharField(blank=True, max_length=256, verbose_name='Additional image CSS class'),
+        ),
+        migrations.AddField(
+            model_name='newsitem',
+            name='image',
+            field=models.ImageField(blank=True, upload_to='news/newsitems/%Y/'),
+        ),
+        migrations.AddField(
+            model_name='newsitem',
+            name='published',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AddField(
+            model_name='newsletternewsitemstable',
+            name='newsitem',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='news.NewsItem'),
+        ),
+        migrations.AddField(
+            model_name='newsletternewsitemstable',
+            name='newsletter',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='news.NewsLetter'),
+        ),
+    ]
diff --git a/news/models.py b/news/models.py
index 3bfdbdbd4c57fbd38c12e8bdd4d4b39512b29519..2e151fde38470a0d1c1b4002d427c5cc7035e9f5 100644
--- a/news/models.py
+++ b/news/models.py
@@ -8,12 +8,39 @@ from django.db import models
 from .managers import NewsManager
 
 
+class NewsLetter(models.Model):
+    """
+    Container of NewsItems.
+    Which NewsItems (and their order) are handled via the auxiliary
+    model NewsLetterNewsItemsTable.
+    """
+    date = models.DateField()
+    intro = models.TextField()
+    closing = models.TextField()
+    published = models.BooleanField(default=False)
+
+    def __str__(self):
+        return 'SciPost Newsletter %s' % self.date.strftime('%Y-%m-%d')
+
+    def get_absolute_url(self):
+        return reverse('news:newsletter_detail',
+                       kwargs={'year': self.date.strftime('%Y'),
+                               'month': self.date.strftime('%m'),
+                               'day': self.date.strftime('%d')})
+
+
 class NewsItem(models.Model):
     date = models.DateField()
     headline = models.CharField(max_length=300)
+    blurb_short = models.TextField(default='',
+                                   help_text='Short version for use in Newsletter/emails etc')
     blurb = models.TextField()
+    image = models.ImageField(upload_to='news/newsitems/%Y/', blank=True)
+    css_class = models.CharField(max_length=256, blank=True,
+                                 verbose_name='Additional image CSS class')
     followup_link = models.URLField(blank=True)
     followup_link_text = models.CharField(max_length=300, blank=True)
+    published = models.BooleanField(default=False)
     on_homepage = models.BooleanField(default=True)
 
     objects = NewsManager()
@@ -27,3 +54,13 @@ class NewsItem(models.Model):
 
     def get_absolute_url(self):
         return reverse('news:news') + '#news_' + str(self.id)
+
+
+class NewsLetterNewsItemsTable(models.Model):
+    """
+    Carries the specification of which NewsItem sits in which NewsLetter,
+    and in which order.
+    """
+    newsletter = models.ForeignKey('news.NewsLetter', on_delete=models.CASCADE)
+    newsitem = models.ForeignKey('news.NewsItem', on_delete=models.CASCADE)
+    order = models.PositiveSmallIntegerField()
diff --git a/news/templates/news/_newsletter_contents.html b/news/templates/news/_newsletter_contents.html
new file mode 100644
index 0000000000000000000000000000000000000000..452b4e972a8d9dcbe532ec8223d09d81b6103242
--- /dev/null
+++ b/news/templates/news/_newsletter_contents.html
@@ -0,0 +1,16 @@
+<div class="row">
+  <div class="col-12">
+    <div class="card card-grey card-news">
+      <h1>SciPost Newsletter {{ nl.date|date:'Y-m-d' }}</h1>
+      <p>{{ nl.intro|safe }}</p>
+    </div>
+    {% for nt in nl.newsletternewsitemstable_set.all|dictsort:'order' %}
+    <div class="card card-grey card-news">
+      {% include 'news/news_card_content.html' with news=nt.newsitem %}
+    </div>
+    {% endfor %}
+    <div class="card card-grey card-news">
+      <p>{{ nl.closing|safe }}</p>
+    </div>
+  </div>
+</div>
diff --git a/news/templates/news/news_card_content.html b/news/templates/news/news_card_content.html
index 134db502bedc715be0da60461a79a7e45e946310..99c6e78098a599e7c31fc4d6b47ae234dfb550e6 100644
--- a/news/templates/news/news_card_content.html
+++ b/news/templates/news/news_card_content.html
@@ -1,11 +1,22 @@
 <div class="card-body news-item" id="news_{{news.id}}">
-    <h2 class="card-title">{{news.headline}}</h2>
-    <div>
-        <div class="text-muted date">{{news.date|date:'j F Y'}}</div>
-        <div class="pb-3">{{news.blurb|safe}}</div>
+  {% if news.image %}
+  <div class="row">
 
-        {% if news.followup_link %}
-            <a href="{{news.followup_link}}">{{news.followup_link_text}}</a>
-        {% endif %}
+    <div class="col-3">
+      <img class="d-flex mr-3 {{ news.image.css_class }}" src="{{ news.image.url }}" alt="image"/>
     </div>
+    <div class="col-9">
+      {% endif %}
+      <div>
+	<h2 class="card-title">{{news.headline}}</h2>
+	<div class="text-muted date">{{news.date|date:'j F Y'}}</div>
+	<div class="pb-3">{{news.blurb|safe}}</div>
+	{% if news.followup_link %}
+	<a href="{{news.followup_link}}">{{news.followup_link_text}}</a>
+	{% endif %}
+      </div>
+      {% if news.image %}
+    </div>
+  </div>
+  {% endif %}
 </div>
diff --git a/news/templates/news/news_manage.html b/news/templates/news/news_manage.html
new file mode 100644
index 0000000000000000000000000000000000000000..282fd278e857a644b5356517a008142624d67b86
--- /dev/null
+++ b/news/templates/news/news_manage.html
@@ -0,0 +1,49 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: News Management{% endblock pagetitle %}
+
+{% block content %}
+<div class="row">
+  <div class="col-12">
+    <h1>NewsLetters</h1>
+    <a href="{% url 'news:newsletter_create' %}">Add a NewsLetter</a>
+    <br/>
+    <ul>
+      {% for nl in newsletters %}
+      <li>
+	<div><a href="{{ nl.get_absolute_url }}">{{ nl }}</a>&nbsp;<strong>Actions:</strong>&nbsp;<a href="{% url 'news:newsletter_update' pk=nl.id %}">Update</a>&nbsp;<a href="{% url 'news:newsletter_delete' pk=nl.id %}">Delete</a>
+	  <h3>Add a News Item to this Newsletter:</h3>
+	    <form class="d-block mt-2 mb-3" action="{% url 'news:add_newsitem_to_newsletter' nlpk=nl.id %}" method="post">
+	      {% csrf_token %}
+	      {{ add_ni_to_nl_form|bootstrap }}
+	      <input type="submit" name="submit" value="Add" class="btn btn-outline-secondary">
+	    </form>
+	</div>
+	<ul>
+	  {% for nt in nl.newsletternewsitemstable_set.all %}
+	  <li>{{ nt.newsitem }}</li>
+	  {% empty %}
+	  <li>No associated NewsItems found</li>
+	  {% endfor %}
+	</ul>
+      </li>
+      {% endfor %}
+    </ul>
+  </div>
+</div>
+
+<div class="row">
+  <div class="col-12">
+    <h1>NewsItems</h1>
+    <a href="{% url 'news:newsitem_create' %}">Add a NewsItem</a>
+    <br/>
+    <ul>
+      {% for ni in newsitems %}
+      <li>{{ ni }} <a href="{% url 'news:newsitem_update' pk=ni.id %}">Update</a>&nbsp;<a href="{% url 'news:newsitem_delete' pk=ni.id %}">Delete</a></li>
+      {% endfor %}
+    </ul>
+  </div>
+</div>
+{% endblock content %}
diff --git a/news/templates/news/newsitem_confirm_delete.html b/news/templates/news/newsitem_confirm_delete.html
new file mode 100644
index 0000000000000000000000000000000000000000..a2d678e164e830abe25fb5da88ff797da8640f1b
--- /dev/null
+++ b/news/templates/news/newsitem_confirm_delete.html
@@ -0,0 +1,25 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: Delete NewsItem{% endblock pagetitle %}
+
+{% block content %}
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Delete NewsItem</h1>
+	{{ object }}
+    </div>
+</div>
+<div class="row">
+  <div class="col-12">
+      <form method="post">
+        {% csrf_token %}
+        <h3 class="mb-2">Are you sure you want to delete this NewsItem?</h3>
+        <input type="submit" class="btn btn-danger" value="Yes, delete it" />
+      </form>
+    </ul>
+  </div>
+</div>
+
+{% endblock content %}
diff --git a/news/templates/news/newsitem_create.html b/news/templates/news/newsitem_create.html
new file mode 100644
index 0000000000000000000000000000000000000000..fafb194eb0675d2a731236c9b4077babaf39140f
--- /dev/null
+++ b/news/templates/news/newsitem_create.html
@@ -0,0 +1,16 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: NewsItems{% endblock pagetitle %}
+
+{% block content %}
+<div class="row">
+  <div class="col-12">
+    <form action="{% url 'news:newsitem_create' %}" method="post" enctype="multipart/form-data">
+      {% csrf_token %}
+      {{ form|bootstrap }}
+      <input type="submit" value="Submit" class="btn btn-primary">
+  </div>
+</div>
+{% endblock content %}
diff --git a/news/templates/news/newsitem_list.html b/news/templates/news/newsitem_list.html
index 01b17aa4c0a1b932bffa869585ae4c24686f4d9a..af456a7bad6786002590219ce970ce214116446b 100644
--- a/news/templates/news/newsitem_list.html
+++ b/news/templates/news/newsitem_list.html
@@ -9,7 +9,10 @@
 
 <div class="row">
     <div class="col-12">
-        <h1 class="highlight">SciPost News</h1>
+      <h1 class="highlight">SciPost News</h1>
+      {% if perms.scipost.can_manage_news %}
+      Go to the <a href="{% url 'news:manage' %}">News management page</a>
+      {% endif %}
     </div>
 </div>
 
@@ -29,9 +32,19 @@
           {% endif %}
 
         {% for item in object_list %}
-            <div class="card card-grey card-news">
+	      {% if item.image %}
+	      <div class="row">
+		<div class="col-3">
+		  <img class="d-flex mr-3 {{ item.image.css_class }}" src="{{ item.image.url }}" alt="image"/>
+		</div>
+		<div class="col-9">
+		{% endif %}
+		<div class="card card-grey card-news">
                 {% include 'news/news_card_content.html' with news=item %}
-            </div>
+		</div>
+		{% if item.image %}
+		</div>
+		{% endif %}
         {% empty %}
             <div>No news found.</div>
         {% endfor %}
diff --git a/news/templates/news/newsitem_update.html b/news/templates/news/newsitem_update.html
new file mode 100644
index 0000000000000000000000000000000000000000..aeaf401b2ea77953c0c780e6f9486538591f14ff
--- /dev/null
+++ b/news/templates/news/newsitem_update.html
@@ -0,0 +1,36 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: NewsItems{% endblock pagetitle %}
+
+{% block breadcrumb_items %}
+{{ block.super }}
+<a href="{% url 'news:news' %}" class="breadcrumb-item">News</a>
+<a href="{% url 'news:manage' %}" class="breadcrumb-item">Manage</a>
+<span class="breadcrumb-item">Update NewsItem</span>
+{% endblock breadcrumb_items %}
+
+
+{% block content %}
+
+<div class="row">
+  <div class="col-12">
+    <h1>News Item to update:</h1>
+    <div class="card card-grey card-news">
+      {% include 'news/news_card_content.html' with news=object %}
+    </div>
+  </div>
+
+  <hr/>
+
+<div class="row">
+  <div class="col-12">
+    <h1>Edit it here:</h1>
+    <form action="{% url 'news:newsitem_update' pk=object.id %}" method="post" enctype="multipart/form-data">
+      {% csrf_token %}
+      {{ form|bootstrap }}
+      <input type="submit" value="Submit" class="btn btn-primary">
+  </div>
+</div>
+{% endblock content %}
diff --git a/news/templates/news/newsletter_confirm_delete.html b/news/templates/news/newsletter_confirm_delete.html
new file mode 100644
index 0000000000000000000000000000000000000000..ccd45b70f26d200fbd3ff50607a465955d6c3f5f
--- /dev/null
+++ b/news/templates/news/newsletter_confirm_delete.html
@@ -0,0 +1,25 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: Delete NewsLetter{% endblock pagetitle %}
+
+{% block content %}
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Delete NewsLetter</h1>
+	{{ object }}
+    </div>
+</div>
+<div class="row">
+  <div class="col-12">
+      <form method="post">
+        {% csrf_token %}
+        <h3 class="mb-2">Are you sure you want to delete this NewsLetter?</h3>
+        <input type="submit" class="btn btn-danger" value="Yes, delete it" />
+      </form>
+    </ul>
+  </div>
+</div>
+
+{% endblock content %}
diff --git a/news/templates/news/newsletter_create.html b/news/templates/news/newsletter_create.html
new file mode 100644
index 0000000000000000000000000000000000000000..1313af25d9e64feded62fe20c49e575c9097ab9f
--- /dev/null
+++ b/news/templates/news/newsletter_create.html
@@ -0,0 +1,16 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: NewsLetters{% endblock pagetitle %}
+
+{% block content %}
+<div class="row">
+  <div class="col-12">
+    <form action="{% url 'news:newsletter_create' %}" method="post">
+      {% csrf_token %}
+      {{ form|bootstrap }}
+      <input type="submit" value="Submit" class="btn btn-primary">
+  </div>
+</div>
+{% endblock content %}
diff --git a/news/templates/news/newsletter_detail.html b/news/templates/news/newsletter_detail.html
new file mode 100644
index 0000000000000000000000000000000000000000..be923e3edb62e9369b89fd51bbaa420378ed8355
--- /dev/null
+++ b/news/templates/news/newsletter_detail.html
@@ -0,0 +1,15 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: NewsLetter{% endblock pagetitle %}
+
+{% block content %}
+
+<div class="row">
+  <div class="col-12">
+    {% include 'news/_newsletter_contents.html' with nl=nl %}
+  </div>
+</div>
+
+{% endblock content %}
diff --git a/news/templates/news/newsletter_update.html b/news/templates/news/newsletter_update.html
new file mode 100644
index 0000000000000000000000000000000000000000..d3c83391a33e0655a111686d9a12fa40c53c314c
--- /dev/null
+++ b/news/templates/news/newsletter_update.html
@@ -0,0 +1,35 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: NewsLetters{% endblock pagetitle %}
+
+{% block breadcrumb_items %}
+{{ block.super }}
+<a href="{% url 'news:news' %}" class="breadcrumb-item">News</a>
+<a href="{% url 'news:manage' %}" class="breadcrumb-item">Manage</a>
+<span class="breadcrumb-item">Update NewsLetter</span>
+{% endblock breadcrumb_items %}
+
+
+{% block content %}
+
+<div class="row">
+  <div class="col-12">
+    <h1>Newsletter to update:</h1>
+    {{ object }}
+  </div>
+</div>
+
+  <hr/>
+
+<div class="row">
+  <div class="col-12">
+    <h1>Edit it here:</h1>
+    <form action="{% url 'news:newsletter_update' pk=object.id %}" method="post">
+      {% csrf_token %}
+      {{ form|bootstrap }}
+      <input type="submit" value="Submit" class="btn btn-primary">
+  </div>
+</div>
+{% endblock content %}
diff --git a/news/urls.py b/news/urls.py
index ac389a486a4510055bbf3b83eb7ecf24248121f8..3c28fe51dc7c993f3f3f300f036f77326fe67451 100644
--- a/news/urls.py
+++ b/news/urls.py
@@ -7,5 +7,32 @@ from django.conf.urls import url
 from . import views
 
 urlpatterns = [
+    url(r'^manage/$',
+        views.NewsManageView.as_view(),
+        name='manage'),
+    url(r'^newsletter/(?P<year>[0-9]{4,})-(?P<month>[0-9]{2,})-(?P<day>[0-9]{2,})/$',
+        views.NewsLetterView.as_view(),
+        name='newsletter_detail'),
+    url(r'^newsletter/add/$',
+        views.NewsLetterCreateView.as_view(),
+        name='newsletter_create'),
+    url(r'^newsletter/(?P<pk>[0-9]+)/update/$',
+        views.NewsLetterUpdateView.as_view(),
+        name='newsletter_update'),
+    url(r'^newsletter/(?P<pk>[0-9]+)/delete/$',
+        views.NewsLetterDeleteView.as_view(),
+        name='newsletter_delete'),
+    url(r'^newsitem/add/$',
+        views.NewsItemCreateView.as_view(),
+        name='newsitem_create'),
+    url(r'^newsitem/(?P<pk>[0-9]+)/update/$',
+        views.NewsItemUpdateView.as_view(),
+        name='newsitem_update'),
+    url(r'^newsitem/(?P<pk>[0-9]+)/delete/$',
+        views.NewsItemDeleteView.as_view(),
+        name='newsitem_delete'),
+    url(r'^add_newsitem_to_newsletter/(?P<nlpk>[0-9]+)/$',
+        views.NewsLetterNewsItemsTableCreateView.as_view(),
+        name='add_newsitem_to_newsletter'),
     url(r'^$', views.NewsListView.as_view(), name='news'),
 ]
diff --git a/news/views.py b/news/views.py
index f56a846d3b60bed5b5348ddf47f9d8ed739ba295..377fd677542ed71f800639cfe1ad009ef9d882f0 100644
--- a/news/views.py
+++ b/news/views.py
@@ -2,9 +2,124 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
+from django.contrib import messages
+from django.core.urlresolvers import reverse_lazy
+from django.shortcuts import get_object_or_404
+from django.views.generic.base import TemplateView
+from django.views.generic.edit import CreateView, UpdateView, DeleteView
+from django.views.generic.detail import DetailView
 from django.views.generic.list import ListView
 
-from .models import NewsItem
+from .models import NewsLetter, NewsItem
+from .forms import NewsLetterForm, NewsItemForm, NewsLetterNewsItemsTableForm
+
+from scipost.mixins import PermissionsMixin
+
+
+class NewsManageView(PermissionsMixin, TemplateView):
+    """
+    General management of News.
+    """
+    permission_required = 'scipost.can_manage_news'
+    template_name = 'news/news_manage.html'
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['newsletters'] = NewsLetter.objects.all()
+        context['newsitems'] = NewsItem.objects.all()
+        context['add_ni_to_nl_form'] = NewsLetterNewsItemsTableForm()
+        return context
+
+
+class NewsLetterView(TemplateView):
+    """
+    Newsletter, for public consumption online.
+    """
+    template_name = 'news/newsletter_detail.html'
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['nl'] = get_object_or_404(NewsLetter,
+                                          date__year=self.kwargs['year'],
+                                          date__month=self.kwargs['month'],
+                                          date__day=self.kwargs['day'])
+        return context
+
+
+class NewsLetterCreateView(PermissionsMixin, CreateView):
+    """
+    Create a NewsLetter.
+    """
+    permission_required = 'scipost.can_manage_news'
+    form_class = NewsLetterForm
+    template_name = 'news/newsletter_create.html'
+    success_url = reverse_lazy('news:manage')
+
+
+class NewsLetterUpdateView(PermissionsMixin, UpdateView):
+    """
+    Update a NewsLetter.
+    """
+    permission_required = 'scipost.can_manage_news'
+    model = NewsLetter
+    form_class = NewsLetterForm
+    template_name = 'news/newsletter_update.html'
+    success_url = reverse_lazy('news:news')
+
+
+class NewsLetterDeleteView(PermissionsMixin, DeleteView):
+    """
+    Delete a NewsLetter.
+    """
+    permission_required = 'scipost.can_manage_news'
+    model = NewsLetter
+    success_url = reverse_lazy('news:news')
+
+
+class NewsItemCreateView(PermissionsMixin, CreateView):
+    """
+    Create a NewsItem.
+    """
+    permission_required = 'scipost.can_manage_news'
+    form_class = NewsItemForm
+    template_name = 'news/newsitem_create.html'
+    success_url = reverse_lazy('news:news')
+
+
+class NewsItemUpdateView(PermissionsMixin, UpdateView):
+    """
+    Update a NewsItem.
+    """
+    permission_required = 'scipost.can_manage_news'
+    model = NewsItem
+    form_class = NewsItemForm
+    template_name = 'news/newsitem_update.html'
+    success_url = reverse_lazy('news:news')
+
+
+class NewsItemDeleteView(PermissionsMixin, DeleteView):
+    """
+    Delete a NewsItem.
+    """
+    permission_required = 'scipost.can_manage_news'
+    model = NewsItem
+    success_url = reverse_lazy('news:news')
+
+
+class NewsLetterNewsItemsTableCreateView(PermissionsMixin, CreateView):
+    """
+    Add a NewsItem to a NewsLetter.
+    """
+    permission_required = 'scipost.can_manage_news'
+    form_class = NewsLetterNewsItemsTableForm
+    success_url = reverse_lazy('news:manage')
+
+    def form_valid(self, form):
+        nl = get_object_or_404(NewsLetter, id=self.kwargs['nlpk'])
+        form.instance.newsletter = nl
+        form.instance.order = nl.newsletternewsitemstable_set.all().count() + 1
+        messages.success(self.request, 'Successfully added News Item to Newsletter')
+        return super().form_valid(form)
 
 
 class NewsListView(ListView):
diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py
index 6e060bd60948f810bfaca8bc1ea7e233e2bf6fca..e035ba9ca26052e6cea4854b453e4240e6c752bc 100644
--- a/scipost/management/commands/add_groups_and_permissions.py
+++ b/scipost/management/commands/add_groups_and_permissions.py
@@ -28,6 +28,7 @@ class Command(BaseCommand):
                                                             name='Registered Contributors')
         Developers, created = Group.objects.get_or_create(name='Developers')
         Testers, created = Group.objects.get_or_create(name='Testers')
+        NewsAdmin, created = Group.objects.get_or_create(name='News Administrators')
         Ambassadors, created = Group.objects.get_or_create(name='Ambassadors')
         JuniorAmbassadors, created = Group.objects.get_or_create(name='Junior Ambassadors')
         ProductionSupervisors, created = Group.objects.get_or_create(name='Production Supervisor')
@@ -280,6 +281,12 @@ class Command(BaseCommand):
             name='Can manage affiliations',
             content_type=content_type)
 
+        # News administration
+        can_manage_news, created = Permission.objects.get_or_create(
+            codename='can_manage_news',
+            name='Can manage News',
+            content_type=content_type)
+
         # Mailchimp
         can_manage_mailchimp, created = Permission.objects.get_or_create(
             codename='can_manage_mailchimp',