We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Emails should contain a link to edit email preferences
Should be to just add a link to the base email template, as well as add a link to mails sent by restricted.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lego/apps/restricted/message_processor.py`
Content:
```
1 from copy import deepcopy
2 from email.message import Message
3 from email.mime.text import MIMEText
4 
5 from django.conf import settings
6 from django.core.mail import get_connection
7 
8 from channels.db import database_sync_to_async
9 from structlog import get_logger
10 
11 from lego.apps.action_handlers.registry import get_handler
12 from lego.apps.restricted.models import RestrictedMail
13 
14 from .message import EmailMessage
15 from .utils import get_mail_token
16 
17 log = get_logger()
18 
19 
20 class MessageProcessor:
21     """
22     Pipeline for message processing after initial parsing is done by the SMTP server.
23     """
24 
25     def __init__(self, sender, message, message_data):
26         self.sender = sender
27         self.message = message
28         self.message_data = message_data
29         self.action_handler = get_handler(RestrictedMail)
30 
31     @database_sync_to_async
32     def process_message(self):
33         token = self.get_token(self.message)
34         if not token:
35             log.critical("restricted_mail_no_token_found", sender=self.sender)
36             # Notify about failure
37             self.action_handler.run(
38                 None, "failure", sender=self.sender, reason="TOKEN_NOT_FOUND"
39             )
40             return None
41 
42         restricted_message = self.lookup_instance(self.sender, token)
43         if restricted_message is None:
44             log.critical("restricted_mail_token_not_found")
45             # Notify about failure
46             self.action_handler.run(
47                 None, "failure", sender=self.sender, reason="TOKEN_INVALID"
48             )
49             return None
50 
51         recipients = restricted_message.lookup_recipients()
52         sender = self.get_sender(restricted_message)
53 
54         message = self.rewrite_message(self.message, sender)
55 
56         if sender == settings.RESTRICTED_FROM:
57             # Add a footer with a note about the from address rewrite.
58             self.decorate(message, restricted_message.hide_sender, self.sender)
59 
60         self.send(recipients, sender, message)
61         restricted_message.mark_used()
62 
63         # Send a success message to the creator
64         self.action_handler.run(restricted_message, "sent")
65 
66     def get_sender(self, restricted_mail):
67         """
68         Get the sender address. We use the global settings and the restricted_mail object to find
69         the sender.
70         """
71 
72         if (
73             settings.RESTRICTED_ALLOW_ORIGINAL_SENDER
74             and not restricted_mail.hide_sender
75         ):
76             return self.sender
77 
78         return settings.RESTRICTED_FROM
79 
80     @staticmethod
81     def get_token(message):
82         """
83         Lookup the attached token, this is used to lookup the existing restricted mail in our
84         database.
85         """
86         return get_mail_token(message)
87 
88     @staticmethod
89     def lookup_instance(sender, token):
90         """
91         Get the restricted_mail instance based on a token found in the received message.
92         """
93         return RestrictedMail.get_restricted_mail(sender, token)
94 
95     @staticmethod
96     def rewrite_message(message, sender):
97         """
98         This function replaces the headers in the message. We preserve the headers in the
99         preserve_headers list, all other headers is removed. We do this to get a higher chance to
100         pass thinks like SPF and DKIM checks. These headers is added automatically by our outgoing
101         mail handler if the sender address is valid and managed by us.
102         """
103         preserve_headers = ["Subject", "Content-Type", "MIME-Version"]
104         headers = {}
105 
106         for header in preserve_headers:
107             header_value = message.get(header)
108             if header_value:
109                 headers[header] = header_value
110 
111         message._headers = []
112 
113         for header, value in headers.items():
114             message[header] = value
115 
116         message["Sender"] = sender
117         message["From"] = sender
118 
119         return message
120 
121     @staticmethod
122     def send(recipients, sender, message):
123         """
124         Create a new connection and bulk send mails
125         """
126         connection = get_connection(fail_silently=False)
127         messages = [
128             EmailMessage(recipient, sender, deepcopy(message))
129             for recipient in recipients
130         ]
131         log.info(
132             "restricted_mail_process_messages", sender=sender, recipients=len(messages)
133         )
134         return connection.send_messages(messages)
135 
136     @staticmethod
137     def decorate(message, hide_sender, sender):
138         """
139         Notify the recipient about the sender rewrite.
140         """
141 
142         footer = ["------------", "Du kan ikke svare direkte på denne eposten."]
143 
144         if not hide_sender:
145             footer.append(
146                 f"Opprinnelig avsender er {sender}, send svar til denne adressen."
147             )
148             footer.append(
149                 "Denne eposten har uorginal avsender for å redusere risikoen for at "
150                 "meldingen oppfattes som spam."
151             )
152         else:
153             footer.append("Opprinnelig avsender har valgt å skjule sin adresse.")
154 
155         footer = "\n".join(footer)
156         charset = message.get_content_charset() or "us-ascii"
157         content_type = message.get_content_type()
158 
159         wrap = True
160         if not message.is_multipart() and content_type == "text/plain":
161             format_param = message.get_param("format")
162             delsp = message.get_param("delsp")
163             transfer_encoding = message.get("content-transfer-encoding")
164 
165             try:
166                 old_payload = message.get_payload(decode=True).decode(charset)
167                 del message["content-transfer-encoding"]
168 
169                 footer_separator = "\n"
170                 payload = old_payload + footer_separator + footer
171 
172                 for cset in (charset, "utf-8"):
173                     try:
174                         message.set_payload(payload.encode(cset), cset)
175                     except UnicodeError:
176                         pass
177                     else:
178                         if format_param:
179                             message.set_param("format", format_param)
180                         if delsp:
181                             message.set_param("delsp", delsp)
182                         wrap = False
183                         break
184             except (LookupError, UnicodeError):
185                 if transfer_encoding:
186                     del message["content-transfer-encoding"]
187                     message["Content-Transfer-Encoding"] = transfer_encoding
188 
189         elif message.get_content_type() == "multipart/mixed":
190             payload = message.get_payload()
191             if not isinstance(payload, list):
192                 payload = [payload]
193 
194             mime_footer = MIMEText(footer.encode("utf-8"), "plain", "utf-8")
195             mime_footer["Content-Disposition"] = "inline"
196             payload.append(mime_footer)
197             message.set_payload(payload)
198             wrap = False
199 
200         if not wrap:
201             return
202 
203         inner = Message()
204         for h, v in message.items():
205             if h.lower().startswith("content-"):
206                 inner[h] = v
207         inner.set_payload(message.get_payload())
208         inner.set_unixfrom(message.get_unixfrom())
209         inner.preamble = message.preamble
210         inner.epilogue = message.epilogue
211         inner.set_default_type(message.get_default_type())
212         if hasattr(message, "__version__"):
213             inner.__version__ = message.__version__
214         payload = [inner]
215         mime_footer = MIMEText(footer.encode("utf-8"), "plain", "utf-8")
216         mime_footer["Content-Disposition"] = "inline"
217         payload.append(mime_footer)
218         message.set_payload(payload)
219         del message["content-type"]
220         del message["content-transfer-encoding"]
221         del message["content-disposition"]
222         message["Content-Type"] = "multipart/mixed"
223
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lego/apps/restricted/message_processor.py b/lego/apps/restricted/message_processor.py
--- a/lego/apps/restricted/message_processor.py
+++ b/lego/apps/restricted/message_processor.py
@@ -149,6 +149,9 @@
             )
         else:
             footer.append("Opprinnelig avsender har valgt å skjule sin adresse.")
+        footer.append(
+            f"Meld deg av her: {settings.FRONTEND_URL}/users/me/settings/notifications"
+        )
 
         footer = "\n".join(footer)
         charset = message.get_content_charset() or "us-ascii"

{"golden_diff": "diff --git a/lego/apps/restricted/message_processor.py b/lego/apps/restricted/message_processor.py\n--- a/lego/apps/restricted/message_processor.py\n+++ b/lego/apps/restricted/message_processor.py\n@@ -149,6 +149,9 @@\n             )\n         else:\n             footer.append(\"Opprinnelig avsender har valgt \u00e5 skjule sin adresse.\")\n+        footer.append(\n+            f\"Meld deg av her: {settings.FRONTEND_URL}/users/me/settings/notifications\"\n+        )\n \n         footer = \"\\n\".join(footer)\n         charset = message.get_content_charset() or \"us-ascii\"\n", "issue": "Emails should contain a link to edit email preferences\nShould be to just add a link to the base email template, as well as add a link to mails sent by restricted.\n", "before_files": [{"content": "from copy import deepcopy\nfrom email.message import Message\nfrom email.mime.text import MIMEText\n\nfrom django.conf import settings\nfrom django.core.mail import get_connection\n\nfrom channels.db import database_sync_to_async\nfrom structlog import get_logger\n\nfrom lego.apps.action_handlers.registry import get_handler\nfrom lego.apps.restricted.models import RestrictedMail\n\nfrom .message import EmailMessage\nfrom .utils import get_mail_token\n\nlog = get_logger()\n\n\nclass MessageProcessor:\n    \"\"\"\n    Pipeline for message processing after initial parsing is done by the SMTP server.\n    \"\"\"\n\n    def __init__(self, sender, message, message_data):\n        self.sender = sender\n        self.message = message\n        self.message_data = message_data\n        self.action_handler = get_handler(RestrictedMail)\n\n    @database_sync_to_async\n    def process_message(self):\n        token = self.get_token(self.message)\n        if not token:\n            log.critical(\"restricted_mail_no_token_found\", sender=self.sender)\n            # Notify about failure\n            self.action_handler.run(\n                None, \"failure\", sender=self.sender, reason=\"TOKEN_NOT_FOUND\"\n            )\n            return None\n\n        restricted_message = self.lookup_instance(self.sender, token)\n        if restricted_message is None:\n            log.critical(\"restricted_mail_token_not_found\")\n            # Notify about failure\n            self.action_handler.run(\n                None, \"failure\", sender=self.sender, reason=\"TOKEN_INVALID\"\n            )\n            return None\n\n        recipients = restricted_message.lookup_recipients()\n        sender = self.get_sender(restricted_message)\n\n        message = self.rewrite_message(self.message, sender)\n\n        if sender == settings.RESTRICTED_FROM:\n            # Add a footer with a note about the from address rewrite.\n            self.decorate(message, restricted_message.hide_sender, self.sender)\n\n        self.send(recipients, sender, message)\n        restricted_message.mark_used()\n\n        # Send a success message to the creator\n        self.action_handler.run(restricted_message, \"sent\")\n\n    def get_sender(self, restricted_mail):\n        \"\"\"\n        Get the sender address. We use the global settings and the restricted_mail object to find\n        the sender.\n        \"\"\"\n\n        if (\n            settings.RESTRICTED_ALLOW_ORIGINAL_SENDER\n            and not restricted_mail.hide_sender\n        ):\n            return self.sender\n\n        return settings.RESTRICTED_FROM\n\n    @staticmethod\n    def get_token(message):\n        \"\"\"\n        Lookup the attached token, this is used to lookup the existing restricted mail in our\n        database.\n        \"\"\"\n        return get_mail_token(message)\n\n    @staticmethod\n    def lookup_instance(sender, token):\n        \"\"\"\n        Get the restricted_mail instance based on a token found in the received message.\n        \"\"\"\n        return RestrictedMail.get_restricted_mail(sender, token)\n\n    @staticmethod\n    def rewrite_message(message, sender):\n        \"\"\"\n        This function replaces the headers in the message. We preserve the headers in the\n        preserve_headers list, all other headers is removed. We do this to get a higher chance to\n        pass thinks like SPF and DKIM checks. These headers is added automatically by our outgoing\n        mail handler if the sender address is valid and managed by us.\n        \"\"\"\n        preserve_headers = [\"Subject\", \"Content-Type\", \"MIME-Version\"]\n        headers = {}\n\n        for header in preserve_headers:\n            header_value = message.get(header)\n            if header_value:\n                headers[header] = header_value\n\n        message._headers = []\n\n        for header, value in headers.items():\n            message[header] = value\n\n        message[\"Sender\"] = sender\n        message[\"From\"] = sender\n\n        return message\n\n    @staticmethod\n    def send(recipients, sender, message):\n        \"\"\"\n        Create a new connection and bulk send mails\n        \"\"\"\n        connection = get_connection(fail_silently=False)\n        messages = [\n            EmailMessage(recipient, sender, deepcopy(message))\n            for recipient in recipients\n        ]\n        log.info(\n            \"restricted_mail_process_messages\", sender=sender, recipients=len(messages)\n        )\n        return connection.send_messages(messages)\n\n    @staticmethod\n    def decorate(message, hide_sender, sender):\n        \"\"\"\n        Notify the recipient about the sender rewrite.\n        \"\"\"\n\n        footer = [\"------------\", \"Du kan ikke svare direkte p\u00e5 denne eposten.\"]\n\n        if not hide_sender:\n            footer.append(\n                f\"Opprinnelig avsender er {sender}, send svar til denne adressen.\"\n            )\n            footer.append(\n                \"Denne eposten har uorginal avsender for \u00e5 redusere risikoen for at \"\n                \"meldingen oppfattes som spam.\"\n            )\n        else:\n            footer.append(\"Opprinnelig avsender har valgt \u00e5 skjule sin adresse.\")\n\n        footer = \"\\n\".join(footer)\n        charset = message.get_content_charset() or \"us-ascii\"\n        content_type = message.get_content_type()\n\n        wrap = True\n        if not message.is_multipart() and content_type == \"text/plain\":\n            format_param = message.get_param(\"format\")\n            delsp = message.get_param(\"delsp\")\n            transfer_encoding = message.get(\"content-transfer-encoding\")\n\n            try:\n                old_payload = message.get_payload(decode=True).decode(charset)\n                del message[\"content-transfer-encoding\"]\n\n                footer_separator = \"\\n\"\n                payload = old_payload + footer_separator + footer\n\n                for cset in (charset, \"utf-8\"):\n                    try:\n                        message.set_payload(payload.encode(cset), cset)\n                    except UnicodeError:\n                        pass\n                    else:\n                        if format_param:\n                            message.set_param(\"format\", format_param)\n                        if delsp:\n                            message.set_param(\"delsp\", delsp)\n                        wrap = False\n                        break\n            except (LookupError, UnicodeError):\n                if transfer_encoding:\n                    del message[\"content-transfer-encoding\"]\n                    message[\"Content-Transfer-Encoding\"] = transfer_encoding\n\n        elif message.get_content_type() == \"multipart/mixed\":\n            payload = message.get_payload()\n            if not isinstance(payload, list):\n                payload = [payload]\n\n            mime_footer = MIMEText(footer.encode(\"utf-8\"), \"plain\", \"utf-8\")\n            mime_footer[\"Content-Disposition\"] = \"inline\"\n            payload.append(mime_footer)\n            message.set_payload(payload)\n            wrap = False\n\n        if not wrap:\n            return\n\n        inner = Message()\n        for h, v in message.items():\n            if h.lower().startswith(\"content-\"):\n                inner[h] = v\n        inner.set_payload(message.get_payload())\n        inner.set_unixfrom(message.get_unixfrom())\n        inner.preamble = message.preamble\n        inner.epilogue = message.epilogue\n        inner.set_default_type(message.get_default_type())\n        if hasattr(message, \"__version__\"):\n            inner.__version__ = message.__version__\n        payload = [inner]\n        mime_footer = MIMEText(footer.encode(\"utf-8\"), \"plain\", \"utf-8\")\n        mime_footer[\"Content-Disposition\"] = \"inline\"\n        payload.append(mime_footer)\n        message.set_payload(payload)\n        del message[\"content-type\"]\n        del message[\"content-transfer-encoding\"]\n        del message[\"content-disposition\"]\n        message[\"Content-Type\"] = \"multipart/mixed\"\n", "path": "lego/apps/restricted/message_processor.py"}], "after_files": [{"content": "from copy import deepcopy\nfrom email.message import Message\nfrom email.mime.text import MIMEText\n\nfrom django.conf import settings\nfrom django.core.mail import get_connection\n\nfrom structlog import get_logger\n\nfrom lego.apps.action_handlers.registry import get_handler\nfrom lego.apps.restricted.models import RestrictedMail\n\nfrom .message import EmailMessage\nfrom .utils import get_mail_token\n\nlog = get_logger()\n\n\nclass MessageProcessor:\n    \"\"\"\n    Pipeline for message processing after initial parsing is done by the LMTP server.\n    \"\"\"\n\n    def __init__(self, sender, message, message_data):\n        self.sender = sender\n        self.message = message\n        self.message_data = message_data\n        self.action_handler = get_handler(RestrictedMail)\n\n    def process_message(self):\n        token = self.get_token(self.message)\n        if not token:\n            log.critical(\"restricted_mail_no_token_found\", sender=self.sender)\n            # Notify about failure\n            self.action_handler.run(\n                None, \"failure\", sender=self.sender, reason=\"TOKEN_NOT_FOUND\"\n            )\n            return None\n\n        restricted_message = self.lookup_instance(self.sender, token)\n        if restricted_message is None:\n            log.critical(\"restricted_mail_token_not_found\")\n            # Notify about failure\n            self.action_handler.run(\n                None, \"failure\", sender=self.sender, reason=\"TOKEN_INVALID\"\n            )\n            return None\n\n        recipients = restricted_message.lookup_recipients()\n        sender = self.get_sender(restricted_message)\n\n        message = self.rewrite_message(self.message, sender)\n\n        if sender == settings.RESTRICTED_FROM:\n            # Add a footer with a note about the from address rewrite.\n            self.decorate(message, restricted_message.hide_sender, self.sender)\n\n        self.send(recipients, sender, message)\n        restricted_message.mark_used()\n\n        # Send a success message to the creator\n        self.action_handler.run(restricted_message, \"sent\")\n\n    def get_sender(self, restricted_mail):\n        \"\"\"\n        Get the sender address. We use the global settings and the restricted_mail object to find\n        the sender.\n        \"\"\"\n\n        if (\n            settings.RESTRICTED_ALLOW_ORIGINAL_SENDER\n            and not restricted_mail.hide_sender\n        ):\n            return self.sender\n\n        return settings.RESTRICTED_FROM\n\n    @staticmethod\n    def get_token(message):\n        \"\"\"\n        Lookup the attached token, this is used to lookup the existing restricted mail in our\n        database.\n        \"\"\"\n        return get_mail_token(message)\n\n    @staticmethod\n    def lookup_instance(sender, token):\n        \"\"\"\n        Get the restricted_mail instance based on a token found in the received message.\n        \"\"\"\n        return RestrictedMail.get_restricted_mail(sender, token)\n\n    @staticmethod\n    def rewrite_message(message, sender):\n        \"\"\"\n        This function replaces the headers in the message. We preserve the headers in the\n        preserve_headers list, all other headers is removed. We do this to get a higher chance to\n        pass thinks like SPF and DKIM checks. These headers is added automatically by our outgoing\n        mail handler if the sender address is valid and managed by us.\n        \"\"\"\n        preserve_headers = [\"Subject\", \"Content-Type\", \"MIME-Version\"]\n        headers = {}\n\n        for header in preserve_headers:\n            header_value = message.get(header)\n            if header_value:\n                headers[header] = header_value\n\n        message._headers = []\n\n        for header, value in headers.items():\n            message[header] = value\n\n        message[\"Sender\"] = sender\n        message[\"From\"] = sender\n\n        return message\n\n    @staticmethod\n    def send(recipients, sender, message):\n        \"\"\"\n        Create a new connection and bulk send mails\n        \"\"\"\n        connection = get_connection(fail_silently=False)\n        messages = [\n            EmailMessage(recipient, sender, deepcopy(message))\n            for recipient in recipients\n        ]\n        log.info(\n            \"restricted_mail_process_messages\", sender=sender, recipients=len(messages)\n        )\n        return connection.send_messages(messages)\n\n    @staticmethod\n    def decorate(message, hide_sender, sender):\n        \"\"\"\n        Notify the recipient about the sender rewrite.\n        \"\"\"\n\n        footer = [\"------------\", \"Du kan ikke svare direkte p\u00e5 denne eposten.\"]\n\n        if not hide_sender:\n            footer.append(\n                f\"Opprinnelig avsender er {sender}, send svar til denne adressen.\"\n            )\n            footer.append(\n                \"Denne eposten har uorginal avsender for \u00e5 redusere risikoen for at \"\n                \"meldingen oppfattes som spam.\"\n            )\n        else:\n            footer.append(\"Opprinnelig avsender har valgt \u00e5 skjule sin adresse.\")\n        footer.append(\n            f\"Meld deg av her: {settings.FRONTEND_URL}/users/me/settings/notifications\"\n        )\n\n        footer = \"\\n\".join(footer)\n        charset = message.get_content_charset() or \"us-ascii\"\n        content_type = message.get_content_type()\n\n        wrap = True\n        if not message.is_multipart() and content_type == \"text/plain\":\n            format_param = message.get_param(\"format\")\n            delsp = message.get_param(\"delsp\")\n            transfer_encoding = message.get(\"content-transfer-encoding\")\n\n            try:\n                old_payload = message.get_payload(decode=True).decode(charset)\n                del message[\"content-transfer-encoding\"]\n\n                footer_separator = \"\\n\"\n                payload = old_payload + footer_separator + footer\n\n                for cset in (charset, \"utf-8\"):\n                    try:\n                        message.set_payload(payload.encode(cset), cset)\n                    except UnicodeError:\n                        pass\n                    else:\n                        if format_param:\n                            message.set_param(\"format\", format_param)\n                        if delsp:\n                            message.set_param(\"delsp\", delsp)\n                        wrap = False\n                        break\n            except (LookupError, UnicodeError):\n                if transfer_encoding:\n                    del message[\"content-transfer-encoding\"]\n                    message[\"Content-Transfer-Encoding\"] = transfer_encoding\n\n        elif message.get_content_type() == \"multipart/mixed\":\n            payload = message.get_payload()\n            if not isinstance(payload, list):\n                payload = [payload]\n\n            mime_footer = MIMEText(footer.encode(\"utf-8\"), \"plain\", \"utf-8\")\n            mime_footer[\"Content-Disposition\"] = \"inline\"\n            payload.append(mime_footer)\n            message.set_payload(payload)\n            wrap = False\n\n        if not wrap:\n            return\n\n        inner = Message()\n        for h, v in message.items():\n            if h.lower().startswith(\"content-\"):\n                inner[h] = v\n        inner.set_payload(message.get_payload())\n        inner.set_unixfrom(message.get_unixfrom())\n        inner.preamble = message.preamble\n        inner.epilogue = message.epilogue\n        inner.set_default_type(message.get_default_type())\n        if hasattr(message, \"__version__\"):\n            inner.__version__ = message.__version__\n        payload = [inner]\n        mime_footer = MIMEText(footer.encode(\"utf-8\"), \"plain\", \"utf-8\")\n        mime_footer[\"Content-Disposition\"] = \"inline\"\n        payload.append(mime_footer)\n        message.set_payload(payload)\n        del message[\"content-type\"]\n        del message[\"content-transfer-encoding\"]\n        del message[\"content-disposition\"]\n        message[\"Content-Type\"] = \"multipart/mixed\"\n", "path": "lego/apps/restricted/message_processor.py"}]}