send flagged message notification to discord
diff --git a/.github/workflows/deploy-to-node.yaml b/.github/workflows/deploy-to-node.yaml
index a86a6385cb..bbfacbb64c 100644
--- a/.github/workflows/deploy-to-node.yaml
+++ b/.github/workflows/deploy-to-node.yaml
@@ -64,6 +64,8 @@ jobs:
       STATS_INTERVAL_WEEK: ${{ vars.STATS_INTERVAL_WEEK }}
       STATS_INTERVAL_MONTH: ${{ vars.STATS_INTERVAL_MONTH }}
       STATS_INTERVAL_TOTAL: ${{ vars.STATS_INTERVAL_TOTAL }}
+      DISCORD_API_KEY: ${{ secrets.DISCORD_API_KEY }}
+      DISCORD_CHANNEL_ID: ${{ vars.DISCORD_CHANNEL_ID }}
       WEB_NEXT_PUBLIC_CLOUDFLARE_CAPTCHA_SITE_KEY:
         ${{ secrets.WEB_NEXT_PUBLIC_CLOUDFLARE_CAPTCHA_SITE_KEY }}
       WEB_CLOUDFLARE_CAPTCHA_SECRET_KEY:
diff --git a/ansible/deploy-to-node.yaml b/ansible/deploy-to-node.yaml
index b0c6efa807..d75311655f 100644
--- a/ansible/deploy-to-node.yaml
+++ b/ansible/deploy-to-node.yaml
@@ -179,6 +179,10 @@
             true) }}"
           BACKEND_CORS_ORIGINS_CSV:
             "{{ lookup('ansible.builtin.env', 'BACKEND_CORS_ORIGINS') }}"
+          DISCORD_API_KEY:
+            "{{ lookup('ansible.builtin.env', 'DISCORD_API_KEY') }}"
+          DISCORD_CHANNEL_ID:
+            "{{ lookup('ansible.builtin.env', 'DISCORD_CHANNEL_ID') }}"
         ports:
           - "{{ backend_port }}:8080"
 
diff --git a/backend/oasst_backend/config.py b/backend/oasst_backend/config.py
index e3d37c7ba0..44721a76ab 100644
--- a/backend/oasst_backend/config.py
+++ b/backend/oasst_backend/config.py
@@ -274,6 +274,9 @@ def validate_user_stats_intervals(cls, v: int):
 
     TASK_VALIDITY_MINUTES: int = 60 * 24 * 2  # tasks expire after 2 days
 
+    DISCORD_API_KEY: str | None = None
+    DISCORD_CHANNEL_ID: str | None = None
+
     class Config:
         env_file = ".env"
         env_file_encoding = "utf-8"
diff --git a/backend/oasst_backend/prompt_repository.py b/backend/oasst_backend/prompt_repository.py
index 26c5cc8168..d7b3ee4133 100644
--- a/backend/oasst_backend/prompt_repository.py
+++ b/backend/oasst_backend/prompt_repository.py
@@ -29,6 +29,7 @@
 from oasst_backend.models.payload_column_type import PayloadContainer
 from oasst_backend.task_repository import TaskRepository, validate_frontend_message_id
 from oasst_backend.user_repository import UserRepository
+from oasst_backend.utils import discord
 from oasst_backend.utils.database_utils import CommitMode, managed_tx_method
 from oasst_shared.exceptions import OasstError, OasstErrorCode
 from oasst_shared.schemas import protocol as protocol_schema
@@ -546,6 +547,8 @@ def store_text_labels(self, text_labels: protocol_schema.TextLabels) -> tuple[Te
                         message_id, protocol_schema.EmojiOp.add, protocol_schema.EmojiCode.red_flag
                     )
 
+                    discord.send_new_report_message(message=message, label_text=text_labels.text, user_id=self.user_id)
+
                 # update existing record for repeated updates (same user no task associated)
                 existing_text_label = self.fetch_non_task_text_labels(message_id, self.user_id)
                 if existing_text_label is not None:
diff --git a/backend/oasst_backend/utils/discord.py b/backend/oasst_backend/utils/discord.py
new file mode 100644
index 0000000000..8b950ec4e1
--- /dev/null
+++ b/backend/oasst_backend/utils/discord.py
@@ -0,0 +1,48 @@
+from uuid import UUID
+
+import requests
+from loguru import logger
+from oasst_backend.config import settings
+from oasst_backend.models.message import Message
+
+ROOT_ENDPOINT = "https://discord.com/api/v10"
+
+
+def send_new_report_message(message: Message, label_text: str, user_id: UUID) -> None:
+    if settings.DISCORD_API_KEY is None or settings.DISCORD_CHANNEL_ID is None:
+        return
+
+    try:
+        logger.debug("Sending flagged message to Discord")
+        message_text = message.text[:500] + "..." if len(message.text) > 500 else message.text
+        label_text = label_text[:4096]  # 4096 is the max length of discord embed description
+        message_content_embed = {
+            "title": "Message content",
+            "description": f"{message_text}",
+            "color": 0x3498DB,  # Blue
+            "footer": {
+                "text": f"Role: {message.role.upper()}\t Lang: {message.lang.upper()}\t 👍{message.emojis.get('+1') or 0} 👎{message.emojis.get('-1') or 0} 🚩{message.emojis.get('red_flag') or 0}"
+            },
+        }
+        label_text_embed = {
+            "title": "Report content",
+            "description": f"{label_text}",
+            "color": 0xE74C3C,  # Red
+            "author": {
+                "name": f"User ID: {user_id}",
+                "url": f"https://open-assistant.io/admin/manage_user/{user_id}",
+            },
+        }
+        res = requests.post(
+            f"{ROOT_ENDPOINT}/channels/{settings.DISCORD_CHANNEL_ID}/messages",
+            headers={
+                "authorization": f"Bot {settings.DISCORD_API_KEY}",
+            },
+            json={
+                "content": f"New flagged message https://open-assistant.io/messages/{message.id}",
+                "embeds": [message_content_embed, label_text_embed],
+            },
+        )
+        res.raise_for_status()
+    except Exception as e:
+        logger.exception(f"Failed to send flagged message. error: {e}")
diff --git a/backend/requirements.txt b/backend/requirements.txt
index f5b2d8b0fb..a788038733 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -15,6 +15,7 @@ pydantic[email]==1.10.4
 python-dotenv==0.21.0
 python-jose[cryptography]==3.3.0
 redis
+requests
 scipy==1.8.1
 SQLAlchemy==1.4.41
 sqlmodel==0.0.8

Send a notification to Discord so the mod team can quickly take action. This is a temporal workaround for https://github.com/LAION-AI/Open-Assistant/issues/968. Also we don't need a UI for it.

![image](https://user-images.githubusercontent.com/33456881/232369181-19e3ec0d-5089-48f7-9d5c-88eb97eab8d3.png)
