diff --git a/templates/django_registration/activate.html b/templates/django_registration/activate.html
deleted file mode 100644
index 39c2bac..0000000
--- a/templates/django_registration/activate.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-
-{% block content %}
-
-{% if account %}
-
-<p>{% trans "Account successfully activated" %}</p>
-
-<p><a href="{% url "login" %}">{% trans "Log in" %}</a></p>
-
-{% else %}
-
-<p>{% trans "Account activation failed" %}</p>
-
-{% endif %}
-
-{% endblock %}
diff --git a/templates/django_registration/activation_email_body.txt b/templates/django_registration/activation_email_body.txt
index 15dd99b..7061136 100644
--- a/templates/django_registration/activation_email_body.txt
+++ b/templates/django_registration/activation_email_body.txt
@@ -7,7 +7,7 @@ link is valid for {{ expiration_days }} days.
 
 {% endblocktrans %}
 
-http://{{ site.domain }}{% url "django_registration_activate" activation_key %}
+http://{{ site.domain }}{% url "django_registration_activate" %}?activation_key={{ activation_key|urlencode }}
 
 {% blocktrans %}
 If you did not make this request, please ignore this message.
diff --git a/templates/django_registration/activation_failed.html b/templates/django_registration/activation_failed.html
deleted file mode 100644
index 07b0cde..0000000
--- a/templates/django_registration/activation_failed.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-
-{% block content %}
-
-{% blocktrans %}
-
-Activation has failed.
-<br/>
-
-{% endblocktrans %}
-
-{% endblock %}
diff --git a/templates/django_registration/activation_form.html b/templates/django_registration/activation_form.html
new file mode 100644
index 0000000..ea4e38f
--- /dev/null
+++ b/templates/django_registration/activation_form.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+{% if activation_error %}
+<div class="alert alert-danger">{{ activation_error.message }}</div>
+{% elif form.errors %}
+<div class="alert alert-danger">{% trans "This activation link is invalid or has expired." %}</div>
+{% else %}
+<p>{% trans "Click the button below to activate your account." %}</p>
+<form id="activation_form" method="post" action="">
+    {% for field in form %}
+        <input type="hidden" name="{{ field.html_name }}" value="{{ field.value|default_if_none:'' }}" />
+    {% endfor %}
+    <input type="submit" class="btn btn-default" value="{% trans 'Activate account' %}" />
+    {% csrf_token %}
+</form>
+{% endif %}
+
+{% endblock %}
diff --git a/tests/test_account_activation.py b/tests/test_account_activation.py
new file mode 100644
index 0000000..3fb56f7
--- /dev/null
+++ b/tests/test_account_activation.py
@@ -0,0 +1,52 @@
+# layerindex-web - tests for the account activation email and page
+#
+# Licensed under the MIT license, see COPYING.MIT for details
+#
+# SPDX-License-Identifier: MIT
+
+import re
+
+import pytest
+from django.contrib.auth.models import User
+from django.core import mail
+from django.test import RequestFactory, TestCase
+from django.urls import reverse
+from django_registration.backends.activation.views import RegistrationView
+
+
+@pytest.mark.django_db
+class TestAccountActivation(TestCase):
+
+    def setUp(self):
+        self.user = User.objects.create_user('newuser', 'new@example.com', 'pw', is_active=False)
+
+    def _activation_path(self):
+        view = RegistrationView()
+        view.request = RequestFactory().get('/')
+        view.send_activation_email(self.user)
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(mail.outbox[0].to, ['new@example.com'])
+        match = re.search(r'https?://[^/\s]+(/\S+)', mail.outbox[0].body)
+        self.assertIsNotNone(match, mail.outbox[0].body)
+        return match.group(1)
+
+    def test_activation_link_activates_account(self):
+        path = self._activation_path()
+        self.assertTrue(path.startswith(reverse('django_registration_activate') + '?activation_key='))
+
+        response = self.client.get(path)
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, 'activation_form')
+
+        key = response.context['form']['activation_key'].value()
+        response = self.client.post(reverse('django_registration_activate'), {'activation_key': key})
+        self.assertRedirects(response, reverse('django_registration_activation_complete'))
+        self.user.refresh_from_db()
+        self.assertTrue(self.user.is_active)
+
+    def test_invalid_key_does_not_activate(self):
+        response = self.client.post(reverse('django_registration_activate'), {'activation_key': 'bogus'})
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, 'invalid or has expired')
+        self.user.refresh_from_db()
+        self.assertFalse(self.user.is_active)
