diff --git a/README.md b/README.md
index f56da5eafaa7e15b8c468de28bf498b76e7bb79e..e45500d395e276bd5c254a156fc524feef8b077a 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ for a given address. uffd-socketmapd uses this API to integrate alias lookup
 with MTAs that support the socketmap protocol, like sendmail and postfix.
 
 uffd-socketmapd can be run manually. For production deployments, use the
-provided debian packages. Add our package mirror to `/etc/sources.list`:
+provided Debian packages. Add our package mirror to `/etc/sources.list`:
 
 ```
 deb https://packages.cccv.de/uffd bullseye main
@@ -26,6 +26,14 @@ by adding the following lines to `/etc/postfix/main.cf`:
 virtual_alias_maps = socketmap:unix:/uffd-socketmapd.sock:virtual
 # Defaults to $virtual_alias_maps, which does not work here, so unset it
 virtual_alias_domains =
+
+# Optional: To use uffd's remailer feature, setup address rewriting. Remailer
+# recipient addresses will be rewritten in both the envelope (like virtual
+# aliases) and the message headers. Make sure that rewriting takes place
+# before DKIM signing and that it is only applied to messages from your
+# services (see local_header_rewrite_clients).
+recipient_canonical_maps = socketmap:unix:/uffd-socketmapd.sock:remailer_canonical
+local_header_rewrite_clients = permit_inet_interfaces permit_sasl_authenticated
 ```
 
 Note that uffd-socketmapd requires at least uffd v1.2.0!
diff --git a/test_server.py b/test_server.py
index 5da2692c61ddf79f56207914be7ae136ffea435d..c43e59df4217d90d5ca2594e128128ce8c07fcf4 100644
--- a/test_server.py
+++ b/test_server.py
@@ -60,14 +60,18 @@ class MockConnection:
 		pass
 
 class UffdAPIMock:
-	def __init__(self, aliases=None):
+	def __init__(self, aliases=None, remailer_map=None):
 		self.aliases = aliases or {}
+		self.remailer_map = remailer_map or {}
 
 	def get_aliases(self, mail_address):
 		return self.aliases.get(mail_address, [])
 
+	def resolve_remailer(self, orig_address):
+		return self.remailer_map.get(orig_address, None)
+
 class TestSocketmapsRequestHandler(unittest.TestCase):
-	def test_handle(self):
+	def test_handle_virtual(self):
 		api = UffdAPIMock(aliases={
 			'test@example.com': ['test-dest@example.com'],
 			'space test@example.com': ['space test dest@example.com'],
@@ -136,3 +140,28 @@ class TestSocketmapsRequestHandler(unittest.TestCase):
 		conn = MockConnection(request, 4096)
 		RequestHandler(conn, '', None).handle()
 		self.assertIn(b'PERM ', conn.sent)
+
+	def test_handle_remailer(self):
+		api = UffdAPIMock(remailer_map={
+			'v1-23-testuser@remailer.example.com': 'testuser@example.com',
+			'v1-23-testadmin@remailer.example.com': 'testadmin@example.com',
+		})
+		RequestHandler = make_requesthandler(api)
+
+		request = b'54:remailer_canonical v1-23-testuser@remailer.example.com,'
+		response = b'23:OK testuser@example.com,'
+		conn = MockConnection(request, 4096)
+		RequestHandler(conn, '', None).handle()
+		self.assertEqual(conn.sent, response)
+
+		request = b'56:remailer_canonical v1-42-not-a-user@remailer.example.com,'
+		response = b'9:NOTFOUND ,'
+		conn = MockConnection(request, 4096)
+		RequestHandler(conn, '', None).handle()
+		self.assertEqual(conn.sent, response)
+
+		request = b'35:remailer_canonical test@example.com,'
+		response = b'9:NOTFOUND ,'
+		conn = MockConnection(request, 4096)
+		RequestHandler(conn, '', None).handle()
+		self.assertEqual(conn.sent, response)
diff --git a/uffd-socketmapd b/uffd-socketmapd
index 76d151a5545842cea97698278b0a58bcd10555c4..63672a83ed34852647a85b45890ca3e9e46cfb5f 100755
--- a/uffd-socketmapd
+++ b/uffd-socketmapd
@@ -30,6 +30,11 @@ class UffdAPI:
 		logger.debug('API getmails response for %s: %s', repr(mail_address), destinations)
 		return destinations
 
+	def resolve_remailer(self, orig_address):
+		'''Return real mail address for a given remailer mail address or None'''
+		result = self.get('/api/v1/resolve-remailer', orig_address=orig_address)
+		return result['address']
+
 # From https://cr.yp.to/proto/netstrings.txt
 #
 # Any string of 8-bit bytes may be encoded as [len]":"[string]",".
@@ -188,6 +193,12 @@ class SocketmapsRequestHandler(socketserver.BaseRequestHandler):
 			# matter, since Postfix internally processes lookup results as a
 			# comma-separated list.
 			return 'OK ' + ','.join(results)
+		if name == 'remailer_canonical':
+			domain = key.rsplit('@', 1)[-1]
+			result = self.api.resolve_remailer(key)
+			if result is None:
+				return 'NOTFOUND '
+			return 'OK ' + result
 		return 'PERM Unknown request name', logging.WARNING
 
 def make_requesthandler(api):