diff --git a/src/memberdb/admin.py b/src/memberdb/admin.py
index 690c4bea85bb5d0c4697bcb4c672d6033bd300f9..9d3da34b0a51ad69132db77c93d7a204a4935bc5 100644
--- a/src/memberdb/admin.py
+++ b/src/memberdb/admin.py
@@ -6,6 +6,7 @@ from django.urls import path, reverse
 from django.utils.html import format_html
 from django.utils.safestring import mark_safe
 from django.template.loader import render_to_string
+from django.contrib import messages
 
 from gms import admin
 
@@ -127,6 +128,7 @@ class MembershipAdmin(admin.ModelAdmin):
 		urls = super().get_urls()
 		custom_urls = [
 			path('<object_id>/approve/', self.admin_site.admin_view(self.process_approve), name='membership-approve'),
+			path('<object_id>/reject/', self.admin_site.admin_view(self.process_reject), name='membership-reject'),
 		]
 		return custom_urls + urls
 
@@ -155,6 +157,7 @@ class MembershipAdmin(admin.ModelAdmin):
 			'member': ms.member,
 			'member_url': get_model_url(ms.member.pk, 'member'),
 			'member_approve': reverse('admin:membership-approve', args=[ms.pk]),
+			'member_reject': reverse('admin:membership-reject', args=[ms.pk]),
 			'create_account': reverse('admin:create-account', args=[ms.member.pk])
 		}
 		html = render_to_string('admin/memberdb/membership_actions.html', context)
@@ -165,6 +168,30 @@ class MembershipAdmin(admin.ModelAdmin):
 
 	def process_approve(self, request, *args, **kwargs):
 		return MembershipApprovalAdminView.as_view(admin=self)(request, *args, **kwargs)
+	
+	def process_reject(self, request, *args, **kwargs):
+		redir = HttpResponseRedirect(reverse('admin:memberdb_membership_changelist', args=[]))
+		try:
+			ms = Membership.objects.get(id=kwargs['object_id'])
+
+			if ms.approved:
+				messages.warning(request, 'Membership is already approved, not rejecting.')
+				return redir
+
+			### TODO send an email or something
+			ms.delete()
+			
+			# DON'T delete the member record, this is too powerful and needs a confirmation page.
+			# TODO confirmation page.
+			#if ms.member.memberships.count() <= 1:
+				# delete the member if there are no other memberships
+				#ms.member.delete()
+				#messages.info(request, '1 membership rejected and 1 member record deleted.')
+			#else:
+			messages.info(request, '1 membership rejected.')
+		except Membership.DoesNotExist:
+			messages.warning(request, 'Membership with id %s does not exist' % kwargs['object_id'])
+		return redir
 
 class ProxyMembership(Membership):
 	"""
diff --git a/src/static/shared.css b/src/static/shared.css
index c226de2a0bed888e63f784cb6b2544a417f06a47..f40fe296bfd5935a7539ecede799ace3e2c4c507 100644
--- a/src/static/shared.css
+++ b/src/static/shared.css
@@ -158,6 +158,17 @@ input[type=button][disabled].default {
     opacity: 0.4;
 }
 
+.button.red {
+    background: #ba2121;
+    color: #fff;
+}
+
+.button.red:active,
+.button.red:focus,
+.button.red:hover {
+    background: #a41515;
+}
+
 .help, p.help, form p.help, div.help, form div.help, div.help li {
     font-size: 12px;
     color: #999;
diff --git a/src/templates/admin/memberdb/membership_actions.html b/src/templates/admin/memberdb/membership_actions.html
index 8bced93e869e7e28a0dc950bfff6e72455cd54a6..1c5bc131c98eb18215ecd0a8db3a4d8e9c454000 100644
--- a/src/templates/admin/memberdb/membership_actions.html
+++ b/src/templates/admin/memberdb/membership_actions.html
@@ -3,6 +3,7 @@
 	<a class="button" href="{{ member_url }}">Edit</a>&nbsp;
 	{% if not ms.approved %}
 	<a class="button" href="{{ member_approve }}">Approve</a>&nbsp;
+	<a class="button red" href="{{ member_reject }}">Reject</a>&nbsp;
 	{% endif %}
 	{% if not member.has_account %}
 	<a class="button" href="{{ create_account }}">Create Account</a>&nbsp;