prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  User avatars don't show in emails
  At least for comment notifications, the avatar of the user who commented is just a blue box with a question mark regardless of whether they have a custom avatar or the default gravatar. We should check if this is happening for other notifications or if it's just the comment workflow email.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/sentry/notifications/utils/avatar.py`
  Content:
  ```
  1 from __future__ import annotations
  2 
  3 from django.urls import reverse
  4 from django.utils.html import format_html
  5 from django.utils.safestring import SafeString
  6 
  7 from sentry.models.avatars.user_avatar import UserAvatar
  8 from sentry.models.user import User
  9 from sentry.services.hybrid_cloud.user import RpcUser
  10 from sentry.utils.assets import get_asset_url
  11 from sentry.utils.avatar import get_email_avatar
  12 from sentry.utils.http import absolute_uri
  13 
  14 
  15 def get_user_avatar_url(user: User | RpcUser, size: int = 20) -> str:
  16     ident: str
  17     if isinstance(user, User):
  18         try:
  19             avatar = UserAvatar.objects.get(user=user)
  20             ident = avatar.ident
  21         except UserAvatar.DoesNotExist:
  22             return ""
  23     elif user.avatar:
  24         if user.avatar is None:
  25             return ""
  26         ident = user.avatar.ident
  27     else:
  28         return ""
  29 
  30     url = reverse("sentry-user-avatar-url", args=[ident])
  31     if size:
  32         url = f"{url}?s={int(size)}"
  33     return str(absolute_uri(url))
  34 
  35 
  36 def get_sentry_avatar_url() -> str:
  37     url = "/images/sentry-email-avatar.png"
  38     return str(absolute_uri(get_asset_url("sentry", url)))
  39 
  40 
  41 def avatar_as_html(user: User | RpcUser) -> SafeString:
  42     if not user:
  43         return format_html(
  44             '<img class="avatar" src="{}" width="20px" height="20px" />', get_sentry_avatar_url()
  45         )
  46     avatar_type = user.get_avatar_type()
  47     if avatar_type == "upload":
  48         return format_html('<img class="avatar" src="{}" />', get_user_avatar_url(user))
  49     elif avatar_type == "letter_avatar":
  50         return get_email_avatar(user.get_display_name(), user.get_label(), 20, False)
  51     else:
  52         return get_email_avatar(user.get_display_name(), user.get_label(), 20, True)
  53
  ```
  
  Path: `src/sentry/notifications/notifications/activity/note.py`
  Content:
  ```
  1 from __future__ import annotations
  2 
  3 from typing import Any, Mapping, Optional
  4 
  5 from sentry.services.hybrid_cloud.actor import RpcActor
  6 from sentry.types.integrations import ExternalProviders
  7 
  8 from .base import GroupActivityNotification
  9 
  10 
  11 class NoteActivityNotification(GroupActivityNotification):
  12     message_builder = "SlackNotificationsMessageBuilder"
  13     metrics_key = "note_activity"
  14     template_path = "sentry/emails/activity/note"
  15 
  16     def get_description(self) -> tuple[str, Optional[str], Mapping[str, Any]]:
  17         # Notes may contain {} characters so we should escape them.
  18         text = str(self.activity.data["text"]).replace("{", "{{").replace("}", "}}")
  19         return text, None, {}
  20 
  21     @property
  22     def title(self) -> str:
  23         if self.user:
  24             author = self.user.get_display_name()
  25         else:
  26             author = "Unknown"
  27         return f"New comment by {author}"
  28 
  29     def get_notification_title(
  30         self, provider: ExternalProviders, context: Mapping[str, Any] | None = None
  31     ) -> str:
  32         return self.title
  33 
  34     def get_message_description(self, recipient: RpcActor, provider: ExternalProviders) -> Any:
  35         return self.get_context()["text_description"]
  36
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/src/sentry/notifications/notifications/activity/note.py b/src/sentry/notifications/notifications/activity/note.py
  --- a/src/sentry/notifications/notifications/activity/note.py
  +++ b/src/sentry/notifications/notifications/activity/note.py
  @@ -2,6 +2,10 @@
   
   from typing import Any, Mapping, Optional
   
  +from django.utils.html import format_html
  +from django.utils.safestring import SafeString
  +
  +from sentry.notifications.utils.avatar import avatar_as_html
   from sentry.services.hybrid_cloud.actor import RpcActor
   from sentry.types.integrations import ExternalProviders
   
  @@ -33,3 +37,15 @@
   
       def get_message_description(self, recipient: RpcActor, provider: ExternalProviders) -> Any:
           return self.get_context()["text_description"]
  +
  +    def description_as_html(self, description: str, params: Mapping[str, Any]) -> SafeString:
  +        """Note emails are formatted differently from almost all other activity emails.
  +        Rather than passing the `description` as a string to be formatted into HTML with
  +        `author` and `an_issue` (see base definition and resolved.py's `get_description`
  +        as an example) we are simply passed the comment as a string that needs no formatting,
  +        and want the avatar on it's own rather than bundled with the author's display name
  +        because the display name is already shown in the notification title."""
  +        fmt = '<span class="avatar-container">{}</span>'
  +        if self.user:
  +            return format_html(fmt, avatar_as_html(self.user, 48))
  +        return format_html(description)
  diff --git a/src/sentry/notifications/utils/avatar.py b/src/sentry/notifications/utils/avatar.py
  --- a/src/sentry/notifications/utils/avatar.py
  +++ b/src/sentry/notifications/utils/avatar.py
  @@ -38,15 +38,18 @@
       return str(absolute_uri(get_asset_url("sentry", url)))
   
   
  -def avatar_as_html(user: User | RpcUser) -> SafeString:
  +def avatar_as_html(user: User | RpcUser, size: int = 20) -> SafeString:
       if not user:
           return format_html(
  -            '<img class="avatar" src="{}" width="20px" height="20px" />', get_sentry_avatar_url()
  +            '<img class="avatar" src="{}" width="{}px" height="{}px" />',
  +            get_sentry_avatar_url(),
  +            size,
  +            size,
           )
       avatar_type = user.get_avatar_type()
       if avatar_type == "upload":
           return format_html('<img class="avatar" src="{}" />', get_user_avatar_url(user))
       elif avatar_type == "letter_avatar":
  -        return get_email_avatar(user.get_display_name(), user.get_label(), 20, False)
  +        return get_email_avatar(user.get_display_name(), user.get_label(), size, False)
       else:
  -        return get_email_avatar(user.get_display_name(), user.get_label(), 20, True)
  +        return get_email_avatar(user.get_display_name(), user.get_label(), size, True)
  
verification_info: {"golden_diff": "diff --git a/src/sentry/notifications/notifications/activity/note.py b/src/sentry/notifications/notifications/activity/note.py\n--- a/src/sentry/notifications/notifications/activity/note.py\n+++ b/src/sentry/notifications/notifications/activity/note.py\n@@ -2,6 +2,10 @@\n \n from typing import Any, Mapping, Optional\n \n+from django.utils.html import format_html\n+from django.utils.safestring import SafeString\n+\n+from sentry.notifications.utils.avatar import avatar_as_html\n from sentry.services.hybrid_cloud.actor import RpcActor\n from sentry.types.integrations import ExternalProviders\n \n@@ -33,3 +37,15 @@\n \n     def get_message_description(self, recipient: RpcActor, provider: ExternalProviders) -> Any:\n         return self.get_context()[\"text_description\"]\n+\n+    def description_as_html(self, description: str, params: Mapping[str, Any]) -> SafeString:\n+        \"\"\"Note emails are formatted differently from almost all other activity emails.\n+        Rather than passing the `description` as a string to be formatted into HTML with\n+        `author` and `an_issue` (see base definition and resolved.py's `get_description`\n+        as an example) we are simply passed the comment as a string that needs no formatting,\n+        and want the avatar on it's own rather than bundled with the author's display name\n+        because the display name is already shown in the notification title.\"\"\"\n+        fmt = '<span class=\"avatar-container\">{}</span>'\n+        if self.user:\n+            return format_html(fmt, avatar_as_html(self.user, 48))\n+        return format_html(description)\ndiff --git a/src/sentry/notifications/utils/avatar.py b/src/sentry/notifications/utils/avatar.py\n--- a/src/sentry/notifications/utils/avatar.py\n+++ b/src/sentry/notifications/utils/avatar.py\n@@ -38,15 +38,18 @@\n     return str(absolute_uri(get_asset_url(\"sentry\", url)))\n \n \n-def avatar_as_html(user: User | RpcUser) -> SafeString:\n+def avatar_as_html(user: User | RpcUser, size: int = 20) -> SafeString:\n     if not user:\n         return format_html(\n-            '<img class=\"avatar\" src=\"{}\" width=\"20px\" height=\"20px\" />', get_sentry_avatar_url()\n+            '<img class=\"avatar\" src=\"{}\" width=\"{}px\" height=\"{}px\" />',\n+            get_sentry_avatar_url(),\n+            size,\n+            size,\n         )\n     avatar_type = user.get_avatar_type()\n     if avatar_type == \"upload\":\n         return format_html('<img class=\"avatar\" src=\"{}\" />', get_user_avatar_url(user))\n     elif avatar_type == \"letter_avatar\":\n-        return get_email_avatar(user.get_display_name(), user.get_label(), 20, False)\n+        return get_email_avatar(user.get_display_name(), user.get_label(), size, False)\n     else:\n-        return get_email_avatar(user.get_display_name(), user.get_label(), 20, True)\n+        return get_email_avatar(user.get_display_name(), user.get_label(), size, True)\n", "issue": "User avatars don't show in emails\nAt least for comment notifications, the avatar of the user who commented is just a blue box with a question mark regardless of whether they have a custom avatar or the default gravatar. We should check if this is happening for other notifications or if it's just the comment workflow email.\n", "before_files": [{"content": "from __future__ import annotations\n\nfrom django.urls import reverse\nfrom django.utils.html import format_html\nfrom django.utils.safestring import SafeString\n\nfrom sentry.models.avatars.user_avatar import UserAvatar\nfrom sentry.models.user import User\nfrom sentry.services.hybrid_cloud.user import RpcUser\nfrom sentry.utils.assets import get_asset_url\nfrom sentry.utils.avatar import get_email_avatar\nfrom sentry.utils.http import absolute_uri\n\n\ndef get_user_avatar_url(user: User | RpcUser, size: int = 20) -> str:\n    ident: str\n    if isinstance(user, User):\n        try:\n            avatar = UserAvatar.objects.get(user=user)\n            ident = avatar.ident\n        except UserAvatar.DoesNotExist:\n            return \"\"\n    elif user.avatar:\n        if user.avatar is None:\n            return \"\"\n        ident = user.avatar.ident\n    else:\n        return \"\"\n\n    url = reverse(\"sentry-user-avatar-url\", args=[ident])\n    if size:\n        url = f\"{url}?s={int(size)}\"\n    return str(absolute_uri(url))\n\n\ndef get_sentry_avatar_url() -> str:\n    url = \"/images/sentry-email-avatar.png\"\n    return str(absolute_uri(get_asset_url(\"sentry\", url)))\n\n\ndef avatar_as_html(user: User | RpcUser) -> SafeString:\n    if not user:\n        return format_html(\n            '<img class=\"avatar\" src=\"{}\" width=\"20px\" height=\"20px\" />', get_sentry_avatar_url()\n        )\n    avatar_type = user.get_avatar_type()\n    if avatar_type == \"upload\":\n        return format_html('<img class=\"avatar\" src=\"{}\" />', get_user_avatar_url(user))\n    elif avatar_type == \"letter_avatar\":\n        return get_email_avatar(user.get_display_name(), user.get_label(), 20, False)\n    else:\n        return get_email_avatar(user.get_display_name(), user.get_label(), 20, True)\n", "path": "src/sentry/notifications/utils/avatar.py"}, {"content": "from __future__ import annotations\n\nfrom typing import Any, Mapping, Optional\n\nfrom sentry.services.hybrid_cloud.actor import RpcActor\nfrom sentry.types.integrations import ExternalProviders\n\nfrom .base import GroupActivityNotification\n\n\nclass NoteActivityNotification(GroupActivityNotification):\n    message_builder = \"SlackNotificationsMessageBuilder\"\n    metrics_key = \"note_activity\"\n    template_path = \"sentry/emails/activity/note\"\n\n    def get_description(self) -> tuple[str, Optional[str], Mapping[str, Any]]:\n        # Notes may contain {} characters so we should escape them.\n        text = str(self.activity.data[\"text\"]).replace(\"{\", \"{{\").replace(\"}\", \"}}\")\n        return text, None, {}\n\n    @property\n    def title(self) -> str:\n        if self.user:\n            author = self.user.get_display_name()\n        else:\n            author = \"Unknown\"\n        return f\"New comment by {author}\"\n\n    def get_notification_title(\n        self, provider: ExternalProviders, context: Mapping[str, Any] | None = None\n    ) -> str:\n        return self.title\n\n    def get_message_description(self, recipient: RpcActor, provider: ExternalProviders) -> Any:\n        return self.get_context()[\"text_description\"]\n", "path": "src/sentry/notifications/notifications/activity/note.py"}], "after_files": [{"content": "from __future__ import annotations\n\nfrom django.urls import reverse\nfrom django.utils.html import format_html\nfrom django.utils.safestring import SafeString\n\nfrom sentry.models.avatars.user_avatar import UserAvatar\nfrom sentry.models.user import User\nfrom sentry.services.hybrid_cloud.user import RpcUser\nfrom sentry.utils.assets import get_asset_url\nfrom sentry.utils.avatar import get_email_avatar\nfrom sentry.utils.http import absolute_uri\n\n\ndef get_user_avatar_url(user: User | RpcUser, size: int = 20) -> str:\n    ident: str\n    if isinstance(user, User):\n        try:\n            avatar = UserAvatar.objects.get(user=user)\n            ident = avatar.ident\n        except UserAvatar.DoesNotExist:\n            return \"\"\n    elif user.avatar:\n        if user.avatar is None:\n            return \"\"\n        ident = user.avatar.ident\n    else:\n        return \"\"\n\n    url = reverse(\"sentry-user-avatar-url\", args=[ident])\n    if size:\n        url = f\"{url}?s={int(size)}\"\n    return str(absolute_uri(url))\n\n\ndef get_sentry_avatar_url() -> str:\n    url = \"/images/sentry-email-avatar.png\"\n    return str(absolute_uri(get_asset_url(\"sentry\", url)))\n\n\ndef avatar_as_html(user: User | RpcUser, size: int = 20) -> SafeString:\n    if not user:\n        return format_html(\n            '<img class=\"avatar\" src=\"{}\" width=\"{}px\" height=\"{}px\" />',\n            get_sentry_avatar_url(),\n            size,\n            size,\n        )\n    avatar_type = user.get_avatar_type()\n    if avatar_type == \"upload\":\n        return format_html('<img class=\"avatar\" src=\"{}\" />', get_user_avatar_url(user))\n    elif avatar_type == \"letter_avatar\":\n        return get_email_avatar(user.get_display_name(), user.get_label(), size, False)\n    else:\n        return get_email_avatar(user.get_display_name(), user.get_label(), size, True)\n", "path": "src/sentry/notifications/utils/avatar.py"}, {"content": "from __future__ import annotations\n\nfrom typing import Any, Mapping, Optional\n\nfrom django.utils.html import format_html\nfrom django.utils.safestring import SafeString\n\nfrom sentry.notifications.utils.avatar import avatar_as_html\nfrom sentry.services.hybrid_cloud.actor import RpcActor\nfrom sentry.types.integrations import ExternalProviders\n\nfrom .base import GroupActivityNotification\n\n\nclass NoteActivityNotification(GroupActivityNotification):\n    message_builder = \"SlackNotificationsMessageBuilder\"\n    metrics_key = \"note_activity\"\n    template_path = \"sentry/emails/activity/note\"\n\n    def get_description(self) -> tuple[str, Optional[str], Mapping[str, Any]]:\n        # Notes may contain {} characters so we should escape them.\n        text = str(self.activity.data[\"text\"]).replace(\"{\", \"{{\").replace(\"}\", \"}}\")\n        return text, None, {}\n\n    @property\n    def title(self) -> str:\n        if self.user:\n            author = self.user.get_display_name()\n        else:\n            author = \"Unknown\"\n        return f\"New comment by {author}\"\n\n    def get_notification_title(\n        self, provider: ExternalProviders, context: Mapping[str, Any] | None = None\n    ) -> str:\n        return self.title\n\n    def get_message_description(self, recipient: RpcActor, provider: ExternalProviders) -> Any:\n        return self.get_context()[\"text_description\"]\n\n    def description_as_html(self, description: str, params: Mapping[str, Any]) -> SafeString:\n        \"\"\"Note emails are formatted differently from almost all other activity emails.\n        Rather than passing the `description` as a string to be formatted into HTML with\n        `author` and `an_issue` (see base definition and resolved.py's `get_description`\n        as an example) we are simply passed the comment as a string that needs no formatting,\n        and want the avatar on it's own rather than bundled with the author's display name\n        because the display name is already shown in the notification title.\"\"\"\n        fmt = '<span class=\"avatar-container\">{}</span>'\n        if self.user:\n            return format_html(fmt, avatar_as_html(self.user, 48))\n        return format_html(description)\n", "path": "src/sentry/notifications/notifications/activity/note.py"}]}