diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 8af89154dd1438a738646e5ee015292fa317b460..3f8ab898b573438cda321888fa8f70ccc7e483d3 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -20,6 +20,7 @@ Added
 * Views filtering for person names now also search the username of a linked user
 * OAuth2 applications now take an icon which is shown in the authorization progress.
 * Add support for hiding the main side nav in ``base.html``.
+* Provide base template and function for sending emails with a template.
 
 Fixed
 ~~~~~
@@ -51,6 +52,7 @@ Changed
   to authorize an external application.
 * Tables can be scrolled horizontally.
 * Overhauled person detail page
+* Use common base template for all emails.
 
 `2.5`_ – 2022-01-02
 -------------------
diff --git a/aleksis/core/celery.py b/aleksis/core/celery.py
index 10457ea9b84871da3038cf4712babb601456bfcd..ab78cfb080ad1abc79f92e873641ef36c22432c9 100644
--- a/aleksis/core/celery.py
+++ b/aleksis/core/celery.py
@@ -1,12 +1,13 @@
 import os
+from traceback import format_exception
 
 from django.conf import settings
 
 from celery import Celery
 from celery.signals import task_failure
-from templated_email import send_templated_mail
 
 from .util.core_helpers import get_site_preferences
+from .util.email import send_email
 
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aleksis.core.settings")
 
@@ -17,10 +18,10 @@ app.autodiscover_tasks()
 
 @task_failure.connect
 def task_failure_notifier(
-    sender=None, task_id=None, exception=None, args=None, traceback=None, **kwargs
+    sender=None, task_id=None, exception=None, args=None, kwargs=None, traceback=None, **__
 ):
     recipient_list = [e[1] for e in settings.ADMINS]
-    send_templated_mail(
+    send_email(
         template_name="celery_failure",
         from_email=get_site_preferences()["mail__address"],
         recipient_list=recipient_list,
@@ -29,8 +30,8 @@ def task_failure_notifier(
             "task": str(sender),
             "task_id": str(task_id),
             "exception": str(exception),
-            "args": str(args),
-            "kwargs": str(kwargs),
-            "traceback": str(traceback),
+            "args": args,
+            "kwargs": kwargs,
+            "traceback": "".join(format_exception(type(exception), exception, traceback)),
         },
     )
diff --git a/aleksis/core/data_checks.py b/aleksis/core/data_checks.py
index 234c82c0ca12b46c7e1aebd7151fb2f2a1c0c2ca..e077f4ccd6175837eedce1814778341fe2772b66 100644
--- a/aleksis/core/data_checks.py
+++ b/aleksis/core/data_checks.py
@@ -8,10 +8,10 @@ from django.utils.translation import gettext as _
 
 import reversion
 from reversion import set_comment
-from templated_email import send_templated_mail
 
 from .util.celery_progress import ProgressRecorder, recorded_task
 from .util.core_helpers import get_site_preferences
+from .util.email import send_email
 
 
 class SolveOption:
@@ -274,9 +274,8 @@ def send_emails_for_data_checks():
         for group in get_site_preferences()["general__data_checks_recipient_groups"]:
             recipient_list += [p.mail_sender for p in group.announcement_recipients if p.email]
 
-        send_templated_mail(
+        send_email(
             template_name="data_checks",
-            from_email=get_site_preferences()["mail__address"],
             recipient_list=recipient_list,
             context={"results": results_with_checks},
         )
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 9c78211eac9dfabe6e1d0b8de8e2034f0dc9da2c..8bb6def6cc619e6729e73b4760ec329b77c46a07 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -45,7 +45,6 @@ from oauth2_provider.models import (
 )
 from phonenumber_field.modelfields import PhoneNumberField
 from polymorphic.models import PolymorphicModel
-from templated_email import send_templated_mail
 
 from aleksis.core.data_checks import BrokenDashboardWidgetDataCheck, DataCheck, DataCheckRegistry
 
@@ -65,6 +64,7 @@ from .mixins import (
 )
 from .tasks import send_notification
 from .util.core_helpers import generate_random_code, get_site_preferences, now_tomorrow
+from .util.email import send_email
 from .util.model_helpers import ICONS
 
 FIELD_CHOICES = (
@@ -386,7 +386,7 @@ class Person(ExtensibleModel):
         recipients = recipients or [
             get_site_preferences()["account__person_change_notification_contact"]
         ]
-        send_templated_mail(
+        send_email(
             template_name="person_changed",
             from_email=self.mail_sender_via,
             headers={
diff --git a/aleksis/core/templates/templated_email/base.email b/aleksis/core/templates/templated_email/base.email
new file mode 100644
index 0000000000000000000000000000000000000000..cce8e7a2fdfc6a005bec62715b8b7646e26b1cea
--- /dev/null
+++ b/aleksis/core/templates/templated_email/base.email
@@ -0,0 +1,21 @@
+{% load i18n %}
+
+{% block subject %}[{{ SITE_PREFERENCES.general__title }}] {% block subject_content %}{% endblock %}{% endblock %}
+
+{% block plain %}{% block plain_greeting %}{% trans "Hello" %},{% endblock %}
+{% block plain_content %}{% endblock %}
+- {{ SITE_PREFERENCES.general__title }}
+{% endblock %}
+
+{% block html %}
+  <style>
+    {% include "templated_email/email.css" %}
+  </style>
+  <body>
+    <div class="main">
+      {% block html_greeting %}<p>{% trans "Hello" %},</p>{% endblock %}
+      {% block html_content %}{% endblock %}
+      <p>- {{ SITE_PREFERENCES.general__title }}</p>
+    </div>
+  </body>
+{% endblock %}
diff --git a/aleksis/core/templates/templated_email/celery_failure.email b/aleksis/core/templates/templated_email/celery_failure.email
index d355823eb70e58f393c6a9006ed2045ac1882171..8718b6f522dfdca868abfbaaae9a6e5032983846 100644
--- a/aleksis/core/templates/templated_email/celery_failure.email
+++ b/aleksis/core/templates/templated_email/celery_failure.email
@@ -1,44 +1,50 @@
+{% extends "templated_email/base.email" %}
 {% load i18n %}
 
-{% block subject %}
- {% blocktrans with task_name=task_name%} Celery task {{ task_name }} failed!{% endblocktrans %}
-{% endblock %}
+{% block subject_content %}{% blocktrans with task_name=task_name %}Celery task {{ task_name }} failed!{% endblocktrans %}{% endblock %}
 
-{% block plain %}
- {% trans "Hello," %}
- {% blocktrans with task_name=task_name %}
-   the celery task {{ task_name }} failed with following information:
- {% endblocktrans %}
+{% block plain_content %}
+{% blocktrans with task_name=task_name %}the celery task {{ task_name }} failed with following information:{% endblocktrans %}
 
+  * {% trans "Task" %}: {{ task_name }}
+  * {% trans "Task ID" %}: {{ task_id }}
+  * {% trans "Raised exception" %}: {{ exception }}
+  * {% trans "Positional arguments" %}:
+    {% for arg in args %}- {{ arg }}
+    {% endfor %}
+  * {% trans "Keyword arguments" %}:
+    {% for key, value in kwargs.items %}- {{ key }}: {{ value }}
+    {% endfor %}
+{{ traceback }}{% endblock %}
 
-{% blocktrans with task_name=task_name task=task task_id=task_id exception=exception args=args kwargs=kwargs traceback=traceback %}
- * Task name: {{task_name}}
- * Task: {{task}}
- * Id of the task: {{task_id}}
- * Exception instance raised: {{ exception }}
- * Positional arguments the task was called with: {{ args }}
- * Keyword arguments the task was called with: {{ kwargs }}
- * Stack trace object: {{ traceback }}
-{% endblocktrans %}
-{% endblock %}
+{% block html_content %}
+  <p>
+    {% blocktrans with task_name=task_name %}
+      the celery task {{ task_name }} failed with following information:
+    {% endblocktrans %}
+  </p>
 
-{% block html %}
- <p>{% trans "Hello," %}</p>
- <p>
-  {% blocktrans with task_name=task_name %}
-    the celery task {{ task_name }} failed with following information:
-  {% endblocktrans %}
- </p>
+  <ul>
+      <li>{% trans "Task" %}: {{ task_name }}</li>
+      <li>{% trans "Task ID" %}: {{ task_id }}</li>
+      <li>{% trans "Raised exception" %}: {{ exception }}</li>
+      <li>{% trans "Positional arguments" %}:
+        <ol>
+          {% for arg in args %}
+            <li>{{ arg }}</li>
+          {% endfor %}
+        </ol>
+      </li>
+      <li>{% trans "Keyword arguments" %}:
+        <ul>
+          {% for key, value in kwargs.items %}
+            <li>{{ key }}: {{ value }}</li>
+          {% endfor %}
+        </ul>
+      </li>
+  </ul>
 
- <ul>
- {% blocktrans with task_name=task_name task=task task_id=task_id exception=exception args=args kwargs=kwargs traceback=traceback %}
-   <li>Task name: {{task_name}}</li>
-   <li>Task: {{task}}</li>
-   <li>Id of the task: {{task_id}}</li>
-   <li>Exception instance raised: {{ exception }}</li>
-   <li>Positional arguments the task was called with: {{ args }}</li>
-   <li>Keyword arguments the task was called with: {{ kwargs }}</li>
-   <li>Stack trace object: {{ traceback }}</li>
- </ul>
- {% endblocktrans %}
+  <code>
+    {{ traceback|linebreaksbr }}
+  </code>
 {% endblock %}
diff --git a/aleksis/core/templates/templated_email/data_checks.css b/aleksis/core/templates/templated_email/data_checks.css
deleted file mode 100644
index b385b185ecfe66dcca070e8eb024bbb091917f04..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/templated_email/data_checks.css
+++ /dev/null
@@ -1,16 +0,0 @@
-body {
-    line-height: 1.5;
-    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
-    font-weight: normal;
-    color: rgba(0, 0, 0, 0.87);
-}
-
-.count {
-    text-align: right;
-    font-family: monospace;
-    font-size: 14pt;
-}
-
-td, th {
-    padding: 10px;
-}
diff --git a/aleksis/core/templates/templated_email/data_checks.email b/aleksis/core/templates/templated_email/data_checks.email
index 0f9b5a5307fba9152683d4e03b0c41a2105629c7..1b3e04960596283327dbb5994fe6e8c6cae83b58 100644
--- a/aleksis/core/templates/templated_email/data_checks.email
+++ b/aleksis/core/templates/templated_email/data_checks.email
@@ -1,27 +1,16 @@
+{% extends "templated_email/base.email" %}
 {% load i18n %}
+{% block subject_content %}{% trans "The system detected some new problems with your data." %}{% endblock %}
 
-{% block subject %}
- {% trans "The system detected some new problems with your data." %}
-{% endblock %}
-
-{% block plain %}
- {% trans "Hello," %}
-
- {% blocktrans %}
-  the system detected some new problems with your data.
-  Please take some time to inspect them and solve the issues or mark them as ignored.
- {% endblocktrans %}
+{% block plain_content %}
+{% blocktrans %}the system detected some new problems with your data.
+Please take some time to inspect them and solve the issues or mark them as ignored.{% endblocktrans %}
 
- {% for result in results %}
-  {{ result.0.problem_name }}: {{ result.1 }}
- {% endfor %}
+{% for result in results %}- {{ result.0.problem_name }}: {{ result.1 }}
+{% endfor %}
 {% endblock %}
 
-{% block html %}
- <style>
-  {% include "templated_email/data_checks.css" %}
- </style>
- <p>{% trans "Hello," %}</p>
+{% block html_content %}
  <p>
   {% blocktrans %}
    the system detected some new problems with your data.
diff --git a/aleksis/core/templates/templated_email/email.css b/aleksis/core/templates/templated_email/email.css
new file mode 100644
index 0000000000000000000000000000000000000000..8cd112624c0c0b78f663027841895f4e07ce608b
--- /dev/null
+++ b/aleksis/core/templates/templated_email/email.css
@@ -0,0 +1,41 @@
+body {
+    line-height: 1.5;
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+    font-weight: normal;
+    color: rgba(0, 0, 0, 0.87);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+table, tr {
+    width: 100%;
+}
+
+.main {
+    max-width: 700px;
+    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
+    -webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
+    -webkit-transition: -webkit-box-shadow .25s;
+    transition: -webkit-box-shadow .25s;
+    transition: box-shadow .25s;
+    transition: box-shadow .25s, -webkit-box-shadow .25s;
+    border-radius: 2px;
+    background-color: #fff;
+    margin: 30px;
+    padding: 20px;
+}
+
+.first th {
+    border-bottom: 1px solid;
+}
+
+
+td, th {
+    padding-left: 5px;
+    padding-right: 5px;
+}
+
+.align-center {
+    text-align: center;
+}
diff --git a/aleksis/core/templates/templated_email/notification.email b/aleksis/core/templates/templated_email/notification.email
index 68a3e79570d1ba93013f2287b6d96cf8679a3572..e1290238f9713829640e6ebc518a618cc61d762f 100644
--- a/aleksis/core/templates/templated_email/notification.email
+++ b/aleksis/core/templates/templated_email/notification.email
@@ -1,31 +1,25 @@
+{% extends "templated_email/base.email" %}
 {% load i18n %}
 
-{% block subject %} {% trans "New notification for" %} {{ notification_user }} {% endblock %}
+{% block subject_content %}{% trans "New notification for" %} {{ notification_user }}{% endblock %}
 
-{% block plain %}
-    {% blocktrans with notification_user=notification_user %}Dear {{ notification_user }},{% endblocktrans %}
+{% block plain_greeting %}{% blocktrans with notification_user=notification_user %}Hello {{ notification_user }},{% endblocktrans %}{% endblock %}
 
-    {% trans "we got a new notification for you:" %}
+{% block plain_content %}
+{% trans "we got a new notification for you:" %}
 
-    {{ notification.title }}
+{{ notification.title }}
 
-    {{ notification.description }}
+{{ notification.description }}{% if notification.link %}
 
-    {% if notification.link %}
-        {% trans "More information" %} → {{ notification.link }}
-    {% endif %}
+{% trans "More information" %} → {{ notification.link }}{% endif %}
 
-    {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %}
-        Sent by {{ trans_sender }} at {{ trans_created_at }}
-    {% endblocktrans %}
-
-    {% trans "Your AlekSIS team" %}
+{% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %}Sent by {{ trans_sender }} at {{ trans_created_at }}{% endblocktrans %}
 {% endblock %}
 
-{% block html %}
-<main>
-    <p>{% blocktrans with notification_user=notification_user %}Dear {{ notification_user }},{% endblocktrans %}</p>
+{% block html_greeting %}<p>{% blocktrans with notification_user=notification_user %}Hello {{ notification_user }},{% endblocktrans %}</p>{% endblock %}
 
+{% block html_content %}
     <p>{% trans "we got a new notification for you:" %}</p>
 
     <blockquote>
@@ -41,9 +35,4 @@
             Sent by {{ trans_sender }} at {{ trans_created_at }}
         {% endblocktrans %}
     </p>
-
-    <p>
-        <i>{% trans "Your AlekSIS team" %}</i>
-    </p>
-</main>
 {% endblock %}
diff --git a/aleksis/core/templates/templated_email/person_changed.email b/aleksis/core/templates/templated_email/person_changed.email
index 1e0d8be25684486bfdceac0b979b461253c21df7..068db0cc67f2b6f55e0f15a5d3e80f06a29594b4 100644
--- a/aleksis/core/templates/templated_email/person_changed.email
+++ b/aleksis/core/templates/templated_email/person_changed.email
@@ -1,23 +1,15 @@
 {% load i18n %}
+{% extends "templated_email/base.email" %}
+{% block subject_content %}{% blocktrans with person=person %}{{ person }} changed their data!{% endblocktrans %}{% endblock %}
 
-{% block subject %}
- {% blocktrans with person=person %}{{ person }} changed their data!{% endblocktrans %}
-{% endblock %}
-
-{% block plain %}
- {% trans "Hello," %}
-
- {% blocktrans with person=person %}
-   the person {{ person }} recently changed the following fields:
- {% endblocktrans %}
+{% block plain_content %}
+{% blocktrans with person=person %}the person {{ person }} recently changed the following fields:{% endblocktrans %}
 
- {% for field in changed_fields %}
-  * {{ field }}
- {% endfor %}
+{% for field in changed_fields %}* {{ field }}
+{% endfor %}
 {% endblock %}
 
 {% block html %}
- <p>{% trans "Hello," %}</p>
  <p>
   {% blocktrans with person=person %}
     the person {{ person }} recently changed the following fields:
diff --git a/aleksis/core/util/email.py b/aleksis/core/util/email.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc7525a7d64e50ec1045dacf02d253ed13b17568
--- /dev/null
+++ b/aleksis/core/util/email.py
@@ -0,0 +1,30 @@
+from typing import Any, Dict, List, Optional
+
+from django.conf import settings
+
+from templated_email import send_templated_mail
+
+from aleksis.core.util.core_helpers import get_site_preferences, process_custom_context_processors
+
+
+def send_email(
+    template_name: str,
+    recipient_list: List[str],
+    context: Dict[str, Any],
+    from_email: Optional[str] = None,
+    **kwargs,
+):
+    """Send templated email with data from context processors."""
+    processed_context = process_custom_context_processors(settings.PDF_CONTEXT_PROCESSORS)
+    processed_context.update(context)
+    if not from_email:
+        from_address = get_site_preferences()["mail__address"]
+        from_name = get_site_preferences()["general__title"]
+        from_email = f"{from_name} <{from_address}>"
+    return send_templated_mail(
+        template_name=template_name,
+        from_email=from_email,
+        recipient_list=recipient_list,
+        context=processed_context,
+        **kwargs,
+    )
diff --git a/aleksis/core/util/notifications.py b/aleksis/core/util/notifications.py
index dac344b7a2e2877d65ff1bc80b580b860bf1067f..13887a197defb9e24f5a9b1538561287b80c942f 100644
--- a/aleksis/core/util/notifications.py
+++ b/aleksis/core/util/notifications.py
@@ -8,9 +8,8 @@ from django.template.loader import get_template
 from django.utils.functional import lazy
 from django.utils.translation import gettext_lazy as _
 
-from templated_email import send_templated_mail
-
 from .core_helpers import lazy_preference
+from .email import send_email
 
 try:
     from twilio.rest import Client as TwilioClient
@@ -35,9 +34,8 @@ def _send_notification_email(notification: "Notification", template: str = "noti
         "notification": notification,
         "notification_user": notification.recipient.addressing_name,
     }
-    send_templated_mail(
+    send_email(
         template_name=template,
-        from_email=lazy_preference("mail", "address"),
         recipient_list=[notification.recipient.email],
         context=context,
     )