diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d7deccbe6dd8890a5e5bad7b545bc54b57912634..47f48832d0f32bbe5069b5fa15d01d6cb12ffe6a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -128,7 +128,7 @@ build_docker:
   script:
     - API_DOMAIN=$(cat $API_DOMAIN_PATH)
     - terraform/environments/scripts/wait-for-ssl.sh "https://${API_DOMAIN}"
-    - BASE_URL="https://${API_DOMAIN}/graphql" /tmp/k6 run smoketest/main.js
+    - BASE_URL="https://${API_DOMAIN}/graphql" k6 run smoketest/main.js
     # TODO should/could we roll back the service to the last working revision on test failure?
 
 review_deploy:
@@ -177,7 +177,7 @@ review_destroy:
     - terraform/environments/scripts/destroy-env.sh $CI_ENVIRONMENT_SLUG
   # can't use rules here: https://gitlab.com/gitlab-org/gitlab/-/issues/34077
   when: manual
-  allow_failure: false
+  allow_failure: true
   except:
     - main
     - production
diff --git a/openbook/settings/__init__.py b/openbook/settings/__init__.py
index abc3d91b90e4c9c78c30b9e162e16cff581a8f6a..c74cddd24e3504bd168c66646a26008ddb93799d 100644
--- a/openbook/settings/__init__.py
+++ b/openbook/settings/__init__.py
@@ -231,6 +231,7 @@ CACHES = {
         "BACKEND": CACHE_BACKENDS["redis"],
         "LOCATION": REDIS_DEFAULT_CACHE_LOCATION,
         "OPTIONS": {
+            "PARSER_CLASS": "redis.connection._HiredisParser",
             "CLIENT_CLASS": "django_redis.client.DefaultClient",
             "REDIS_CLIENT_KWARGS": {"health_check_interval": 30},
         },
@@ -241,6 +242,7 @@ CACHES = {
         "BACKEND": CACHE_BACKENDS["redis"],
         "LOCATION": REDIS_USERBLOCK_CACHE_LOCATION,
         "OPTIONS": {
+            "PARSER_CLASS": "redis.connection._HiredisParser",
             "CLIENT_CLASS": "django_redis.client.DefaultClient",
             "REDIS_CLIENT_KWARGS": {"health_check_interval": 30},
         },
@@ -251,19 +253,24 @@ CACHES = {
         "BACKEND": CACHE_BACKENDS["redis"],
         "LOCATION": REDIS_UNIFIED_API_RESPONSE_CACHE_LOCATION,
         "OPTIONS": {
+            "PARSER_CLASS": "redis.connection._HiredisParser",
             "CLIENT_CLASS": "django_redis.client.DefaultClient",
             "SERIALIZER": "django_redis.serializers.json.JSONSerializer",
             "REDIS_CLIENT_KWARGS": {"health_check_interval": 30},
+            "SOCKET_TIMEOUT": 5,
+            "SOCKET_CONNECT_TIMEOUT": 5,
         },
         "KEY_PREFIX": "response-cache",
         "KEY_FUNCTION": "openbook.utils.unified_api_response_cache_key_function",
         "REVERSE_KEY_FUNCTION": "openbook.utils.unified_api_response_cache_reverse_key",
         "TIMEOUT": 60 * 60,
+        "CONNECTION_POOL_CLASS_KWARGS": {"retry_on_timeout": True},
     },
     RQ_DEFAULT_JOBS: {
         "BACKEND": CACHE_BACKENDS["redis"],
         "LOCATION": REDIS_RQ_DEFAULT_JOBS_CACHE_LOCATION,
         "OPTIONS": {
+            "PARSER_CLASS": "redis.connection._HiredisParser",
             "CLIENT_CLASS": "django_redis.client.DefaultClient",
             "REDIS_CLIENT_KWARGS": {"health_check_interval": 30},
         },
@@ -273,6 +280,7 @@ CACHES = {
         "BACKEND": CACHE_BACKENDS["redis"],
         "LOCATION": REDIS_RQ_HIGH_JOBS_CACHE_LOCATION,
         "OPTIONS": {
+            "PARSER_CLASS": "redis.connection._HiredisParser",
             "CLIENT_CLASS": "django_redis.client.DefaultClient",
             "REDIS_CLIENT_KWARGS": {"health_check_interval": 30},
         },
@@ -282,6 +290,7 @@ CACHES = {
         "BACKEND": CACHE_BACKENDS["redis"],
         "LOCATION": REDIS_RQ_LOW_JOBS_CACHE_LOCATION,
         "OPTIONS": {
+            "PARSER_CLASS": "redis.connection._HiredisParser",
             "CLIENT_CLASS": "django_redis.client.DefaultClient",
             "REDIS_CLIENT_KWARGS": {"health_check_interval": 30},
         },
diff --git a/openbook_notifications/notifications.py b/openbook_notifications/notifications.py
index 9cd5c2a71e25a6d375beb195995bf6d88fee9059..61be89329e714a7530b09a90e5a1bc96462edb4f 100644
--- a/openbook_notifications/notifications.py
+++ b/openbook_notifications/notifications.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import json
 import uuid
 from enum import Enum
 from typing import List, Optional
@@ -10,7 +11,6 @@ from requests import Session
 from requests.adapters import HTTPAdapter, Retry
 from requests.exceptions import ConnectionError
 
-from openbook_notifications.helpers import flatten_payload
 from openbook_notifications.payloads import (
     PostCommentAuthorPayload,
     PostCommentMentionPayload,
@@ -74,9 +74,16 @@ class Notification:
         self.session = session
         self.transaction_id = transaction_id
 
+        # Android notifications only allow for key/value pairs in the data field to be of type string, not objects/JSON
+        # (see https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#androidconfig).
+        # That is why we serialize the nested data object to a string and deserialize back to JSON it in the mobile client.
+        android_payload = {k: v for k, v in payload.items() if not isinstance(v, dict)}
+        android_payload["data"] = json.dumps(payload.get("data"))
+        # iOS allows to use full JSON-serializable objects as payload
+        # (see https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#apnsconfig)
         self.overrides = {
             "type": "data",  # data notification
-            "fcm": {"type": "notification", "data": flatten_payload(payload)},
+            "fcm": {"type": "notification", "data": android_payload},
             "apns": {"payload": payload},
         }
 
diff --git a/openbook_notifications/tests/test_notifications.py b/openbook_notifications/tests/test_notifications.py
index eae4396fed40f5cd8254937460dd008d588815cb..692ff2f0651a369264da5560dc062774f74031a3 100644
--- a/openbook_notifications/tests/test_notifications.py
+++ b/openbook_notifications/tests/test_notifications.py
@@ -57,16 +57,20 @@ def mock_novu_api():
 class NotificationTestCase(TestCase):
     @classmethod
     def setUpTestData(cls):
+        cls.payload = {"data": {"key": "value", "nested": {"nestedKey": "nestedValue"}}}
         cls.notification_instance = Notification(
             workflow="test-workflow",
             recipients=["user-id"],
-            payload={"data": {"key": "value"}},
+            payload=cls.payload,
             transaction_id="test-transaction-id",
         )
         cls.overrides = {
             "type": "data",
-            "fcm": {"type": "notification", "data": {"key": "value"}},
-            "apns": {"payload": {"data": {"key": "value"}}},
+            "fcm": {
+                "type": "notification",
+                "data": {"data": '{"key": "value", "nested": {"nestedKey": "nestedValue"}}'},
+            },
+            "apns": {"payload": cls.payload},
         }
 
     @patch.object(EventApi, "trigger")
@@ -77,7 +81,7 @@ class NotificationTestCase(TestCase):
         mock_event_api_trigger.assert_called_with(
             name="test-workflow",
             recipients=["user-id"],
-            payload={"data": {"key": "value"}},
+            payload=self.payload,
             overrides=self.overrides,
             transaction_id="test-transaction-id",
         )
diff --git a/renovate.json b/renovate.json
index 3ae1faafc3916e4a27b3af156c71284e69f262d5..4c06317c105cb37ea99349699b427d034a2a4bcb 100644
--- a/renovate.json
+++ b/renovate.json
@@ -36,6 +36,13 @@
       ],
       "automerge": true
     },
+    {
+      "matchDepNames": [
+        "boto3",
+        "botocore"
+      ],
+      "groupName": "boto3"
+    },
     {
       "matchDepTypes": [
         "devDependencies"
diff --git a/requirements.txt b/requirements.txt
index ee3f86d23a739705b0aa485b8560695639fbf10d..43ac9469cae1878acdeccb0c6e1111819ae9492b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,12 +22,12 @@ bandit~=1.7.9
 beautifulsoup4~=4.12.3
 black~=24.10.0
 blurhash-python~=1.2.2
-boto3~=1.34.154
-botocore~=1.34.154
-cachetools~=5.4.0
+boto3~=1.35.46
+botocore~=1.35.46
+cachetools~=5.5.0
 certifi~=2024.8.30
 cffi~=1.17.0
-charset-normalizer~=3.3.2
+charset-normalizer~=3.4.0
 click~=8.1.3
 colorama~=0.4.6
 colorlog~=6.8.2
@@ -38,7 +38,7 @@ Django~=5.0.7
 django-admin-rangefilter~=0.13.1
 django-appconf~=1.0.6
 django-cacheops~=7.0.2
-django-cors-headers~=4.4.0
+django-cors-headers~=4.5.0
 django-cursor-pagination~=0.3.0
 django-debug-toolbar~=4.4.6
 django-extensions~=3.2.3
@@ -58,22 +58,23 @@ djangorestframework-camel-case~=1.4.2
 dparse~=0.6.4b0
 execnet~=2.1.1
 Faker~=12.0.1 # mixer 7.2.2 depends on Faker<12.1 and >=5.4.0
-filelock~=3.15.4
-frozenlist~=1.4.1
+filelock~=3.16.1
+frozenlist~=1.5.0
 funcy~=2.0
 gitdb~=4.0.11
 GitPython~=3.1.43
-google-api-core~=2.19.1
-google-auth~=2.33.0
-google-cloud-pubsub~=2.23.0
+google-api-core~=2.21.0
+google-auth~=2.35.0
+google-cloud-pubsub~=2.26.1
 google-cloud-webrisk~=1.14.5
-googleapis-common-protos~=1.63.0
+googleapis-common-protos~=1.65.0
 graphql-core~=3.2.3
 grpc-google-iam-v1~=0.13.0
-grpcio~=1.65.4
+grpcio~=1.67.0
 grpcio-status~=1.62.1
 h11~=0.14.0
 halo~=0.0.31
+hiredis~=3.0.0
 httpcore~=1.0.5
 httpx~=0.27.0
 icalendar~=5.0.13
@@ -86,10 +87,10 @@ langdetect~=1.0.9
 log-symbols~=0.0.14
 markdown-it-py~=3.0.0
 MarkupSafe~=2.1.5
-marshmallow~=3.21.3
+marshmallow~=3.23.0
 mdurl~=0.1.2
 mixer~=7.2.2
-multidict~=6.0.5
+multidict~=6.1.0
 mypy-extensions~=1.0.0
 nose~=1.3.7
 nose-exclude~=0.5.0
@@ -97,14 +98,14 @@ novu~=1.14.0
 packaging~=24.1
 pathspec~=0.12.1
 pathtools~=0.1.2
-pbr~=6.0.0
+pbr~=6.1.0
 pilkit~=3.0
 pillow~=10.4.0
 pinocchio~=0.4.3
-platformdirs~=4.2.0
+platformdirs~=4.3.6
 pluggy~=1.5.0
 posthog==3.7.0
-proto-plus~=1.24.0
+proto-plus~=1.25.0
 protobuf~=4.25.3
 psycopg~=3.2.1
 psycopg-binary~=3.2.1
@@ -115,11 +116,11 @@ pydantic~=2.8.2
 pydantic_core~=2.20.1
 Pygments~=2.18.0
 PyJWT~=2.9.0
-pyparsing~=3.1.2
+pyparsing~=3.2.0
 pytest~=8.3.2
-pytest-asyncio~=0.23.8
+pytest-asyncio~=0.24.0
 pytest-cov~=5.0.0
-pytest-django~=4.8.0
+pytest-django~=4.9.0
 pytest-xdist~=3.6.1
 python-benedict~=0.33.2
 python-dateutil~=2.9.0.post0