diff --git a/apps/mobile/navigation/RootNavigator.tsx b/apps/mobile/navigation/RootNavigator.tsx
index d2227b774469be3a4dc5ed526a5c7885c9abb8b0..3d3d1cebb9232fa9e1fbf70681a83749b27b3f55 100644
--- a/apps/mobile/navigation/RootNavigator.tsx
+++ b/apps/mobile/navigation/RootNavigator.tsx
@@ -33,6 +33,7 @@ import type { SearchParams as OldSearchParams } from '@holi/core/screens/search/
 import UserProfile, { type UserProfileParams } from '@holi/core/screens/userprofile/UserProfile'
 import NotFound from '@holi/core/screens/404/NotFound'
 import { AuthSelection } from '@holi/core/auth/screens/AuthSelection'
+import Unsubscribe from '@holi/core/screens/mail/Unsubscribe'
 
 export type RootStackScreens = {
   [RouteName.BottomTabs]: undefined
@@ -67,6 +68,8 @@ export type RootStackScreens = {
 
   [RouteName.AuthSelection]: undefined
 
+  [RouteName.Unsubscribe]: undefined
+
   [RouteName.Search]: SearchParams | OldSearchParams
 
   [RouteName.UserProfile]: UserProfileParams
@@ -266,6 +269,8 @@ const RootNavigator = () => {
           headerLeft: () => <HeaderBackButton />,
         }}
       />
+
+      <RootStack.Screen name={RouteName.Unsubscribe} component={Unsubscribe} options={{ headerShown: true }} />
     </RootStack.Navigator>
   )
 }
diff --git a/apps/mobile/navigation/linkingConfig.ts b/apps/mobile/navigation/linkingConfig.ts
index b8d675400f59cb6de9a9deff91c2f09a2d7faaa0..1ea20a8e35b8b2c5f6e6cc4195bc3b6fb62daf44 100644
--- a/apps/mobile/navigation/linkingConfig.ts
+++ b/apps/mobile/navigation/linkingConfig.ts
@@ -182,6 +182,7 @@ const linkingConfig: LinkingOptions<RootStackScreens>['config'] = {
     [RouteName.UserProfile]: 'profile/:userId',
     [RouteName.NotFound]: '404',
     [RouteName.AuthSelection]: '/authSelection',
+    [RouteName.Unsubscribe]: 'unsubscribe',
   },
 }
 
diff --git a/apps/mobile/navigation/routeName.ts b/apps/mobile/navigation/routeName.ts
index 0142ac9396242dbebc8fb3d3a1b379e71e93c623..1e4b93775b882b43c9babc22b5f5aef9e0c99cbd 100644
--- a/apps/mobile/navigation/routeName.ts
+++ b/apps/mobile/navigation/routeName.ts
@@ -117,6 +117,7 @@ export enum RouteName {
   VolunteeringApp = 'VolunteeringApp',
   NotFound = 'NotFound',
   AuthSelection = 'AuthSelection',
+  Unsubscribe = 'Unsubscribe',
 
   // Onboarding Stack
   Onboarding = 'Onboarding',
diff --git a/apps/web/pages/unsubscribe.tsx b/apps/web/pages/unsubscribe.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..691a955dcccdc1e0690d585b1f6a1bf0c90aa5b2
--- /dev/null
+++ b/apps/web/pages/unsubscribe.tsx
@@ -0,0 +1,8 @@
+import type { NextPage } from 'next'
+import React from 'react'
+
+import Unsubscribe from '@holi/core/screens/mail/Unsubscribe'
+
+const UnsubscribePage: NextPage = () => <Unsubscribe />
+
+export default UnsubscribePage
diff --git a/core/i18n/locales/de.json b/core/i18n/locales/de.json
index e33b8c71eb91673c6ae7470758274c62c83a4896..c11db9b807d28c84202927b24763c4e3cd740ba7 100644
--- a/core/i18n/locales/de.json
+++ b/core/i18n/locales/de.json
@@ -214,6 +214,11 @@
     "Freitag",
     "Samstag"
   ],
+  "email.unsubscribe.description": "Du hast dich erfolgreich als \"{{email}}\" bei {{series}} abgemeldet.",
+  "email.unsubscribe.welcomeSeries": "Begrüßungsserie",
+  "email.unsubscribe.subText": "Hast du dich versehentlich abgemeldet? Du kannst ganz einfach zum alten Zustand zurückkehren.",
+  "email.unsubscribe.button": "Melde dich wieder bei der Email-Serie an",
+  "email.resubscribe.description": "Du hast dich erfolgreich als \"{{email}}\" bei {{series}} angemeldet.",
   "error.errorBoundary.retry": "Erneut versuchen?",
   "favorites.noResults.description": "Tippe aufs Lesezeichen-Symbol, um Inhalte zu deinen Favoriten hinzuzufügen.",
   "favorites.noResults.title": "No keine Favoriten gespeichert",
diff --git a/core/i18n/locales/en.json b/core/i18n/locales/en.json
index f425c3dd4a5ddd8becd2cd16412d94e15ff989ba..8e2dfde968b4664092d31587334458e55a103179 100644
--- a/core/i18n/locales/en.json
+++ b/core/i18n/locales/en.json
@@ -214,6 +214,11 @@
     "Friday",
     "Saturday"
   ],
+  "email.unsubscribe.description": "You have successfully unsubscribed from the email {{series}} as \"{{email}}\".",
+  "email.unsubscribe.welcomeSeries": "welcome series",
+  "email.unsubscribe.subText": "Did you unsubscribe by mistake? You can easily go back to how it was.",
+  "email.unsubscribe.button": "Subscribe again to the email series",
+  "email.resubscribe.description": "You have successfully re-subscribed to the email {{series}} as \"{{email}}\".",
   "error.errorBoundary.retry": "Try again?",
   "favorites.noResults.description": "Tap the bookmark icon on content you like to add them to your favourites.",
   "favorites.noResults.title": "No favourites saved yet",
diff --git a/core/screens/mail/Unsubscribe.tsx b/core/screens/mail/Unsubscribe.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..937a9f9fe1d8aeccc1fa63fed3c1e75dec67b76d
--- /dev/null
+++ b/core/screens/mail/Unsubscribe.tsx
@@ -0,0 +1,12 @@
+import type React from 'react'
+
+export type UnsubscribeParams = {
+  token: string
+  email: string
+  series: string
+}
+
+export const Unsubscribe: React.FC = () => {
+  return <></>
+}
+export default Unsubscribe
diff --git a/core/screens/mail/Unsubscribe.web.tsx b/core/screens/mail/Unsubscribe.web.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b4340495d649b690d72a1f8d16d524e042e5ac50
--- /dev/null
+++ b/core/screens/mail/Unsubscribe.web.tsx
@@ -0,0 +1,111 @@
+import type React from 'react'
+import { Platform, Pressable, StyleSheet, View } from 'react-native'
+import useRouting from '@holi/core/navigation/hooks/useRouting'
+import { HoliGap } from '@holi/ui/components/atoms/HoliGap'
+import HoliLogo from '@holi/ui/components/atoms/HoliLogo'
+import { useTheme, type HoliTheme } from '@holi/ui/styles/theme'
+import Spacing from '@holi/ui/foundations/Spacing'
+import { useSetScreenOptions } from '@holi/core/navigation/hooks/useScreenOptions'
+import useIsomorphicLayoutEffect from '@holi/core/helpers/useIsomorphicLayoutEffect'
+import { maskEmail } from '@holi/core/screens/mail/utils/maskEmail'
+import { useEffect, useState } from 'react'
+import { EmailIllustration } from './components/EmailIllustration'
+import { EmailContent } from './components/EmailContent'
+import { useEmailSubscriptionAnimation } from './hooks/useEmailSubscriptionAnimation'
+import { unsubscribeFromNewsletterMutation } from '@holi/core/screens/mail/mutations'
+import { useMutation } from '@apollo/client'
+import createParamHooks from '@holi/core/navigation/hooks/useParam'
+
+export type UnsubscribeParams = {
+  token: string
+  email: string
+  series: string
+}
+
+const { useParam } = createParamHooks<UnsubscribeParams>()
+
+export const Unsubscribe: React.FC = () => {
+  const [token] = useParam('token')
+  const [email] = useParam('email')
+  const [series] = useParam('series')
+
+  const { navigate } = useRouting()
+  const { theme } = useTheme()
+  const setScreenOptions = useSetScreenOptions()
+  const [isResubscribed, setIsResubscribed] = useState(false)
+  const { fadeAnim, animateTransition } = useEmailSubscriptionAnimation()
+  const maskedEmail = email ? maskEmail(email as string) : ''
+  const styles = getStyles(theme)
+
+  const [unsubscribe] = useMutation(unsubscribeFromNewsletterMutation, {
+    variables: {
+      token,
+    },
+  })
+
+  useEffect(() => {
+    if (token) {
+      unsubscribe()
+    }
+  }, [unsubscribe, token])
+
+  useIsomorphicLayoutEffect(() => {
+    setScreenOptions({
+      headerBackVisible: false,
+      headerShown: false,
+      headerStyle: {
+        backgroundColor: theme.colors.white200,
+      },
+    })
+  }, [setScreenOptions, theme.colors.white200])
+
+  const handleLoginPress = () => {
+    navigate('/login')
+  }
+
+  const handleResubscribe = () => {
+    // TODO: Implement resubscribe functionality
+
+    animateTransition(() => setIsResubscribed(true))
+  }
+
+  return (
+    <View style={styles.contentContainer}>
+      <HoliGap size="xs" />
+
+      <Pressable onPress={() => navigate('/')}>
+        <View style={styles.logo}>
+          <HoliLogo color={theme.colors.black300} />
+        </View>
+      </Pressable>
+
+      <HoliGap size="l" />
+
+      <EmailIllustration isResubscribed={isResubscribed} fadeAnim={fadeAnim} />
+
+      <EmailContent
+        isResubscribed={isResubscribed}
+        fadeAnim={fadeAnim}
+        series={series || ''}
+        email={maskedEmail}
+        onResubscribe={handleResubscribe}
+        onLogin={handleLoginPress}
+      />
+    </View>
+  )
+}
+
+const getStyles = (theme: HoliTheme) =>
+  StyleSheet.create({
+    contentContainer: {
+      flex: 1,
+      paddingHorizontal: Platform.OS === 'web' ? 0 : 24,
+      backgroundColor: theme.colors.white200,
+      paddingTop: Spacing['4xl'],
+    },
+    logo: {
+      alignItems: 'flex-start',
+    },
+  })
+
+export default Unsubscribe
diff --git a/core/screens/mail/components/EmailContent.tsx b/core/screens/mail/components/EmailContent.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..15a31bc551f74f89373f6c4e23e03f98b03b57c3
--- /dev/null
+++ b/core/screens/mail/components/EmailContent.tsx
@@ -0,0 +1,84 @@
+import type React from 'react'
+import { Animated, Pressable, StyleSheet } from 'react-native'
+import { Text } from 'holi-bricks/components/text/Text'
+import { Button } from 'holi-bricks/components/button/Button'
+import { HoliGap } from '@holi/ui/components/atoms/HoliGap'
+import { HoliIcon } from '@holi/icons/src/HoliIcon'
+import { Refresh } from '@holi/icons/src/generated'
+import Spacing from '@holi/ui/foundations/Spacing'
+import { useTheme } from '@holi/ui/styles/theme'
+import { useTranslation } from 'react-i18next'
+
+interface EmailContentProps {
+  isResubscribed: boolean
+  fadeAnim?: Animated.Value
+  series: string
+  email: string
+  onResubscribe: () => void
+  onLogin: () => void
+}
+
+export const EmailContent: React.FC<EmailContentProps> = ({
+  isResubscribed,
+  fadeAnim,
+  series,
+  email,
+  onResubscribe,
+  onLogin,
+}) => {
+  const { theme } = useTheme()
+  const { t } = useTranslation()
+
+  let fullSeriesName = series
+
+  switch (series) {
+    case 'welcome':
+      fullSeriesName = t('email.unsubscribe.welcomeSeries')
+      break
+  }
+
+  return (
+    <Animated.View
+      style={{
+        opacity: fadeAnim,
+      }}
+    >
+      <Text size="xxl" color="main" textAlign="left">
+        {isResubscribed
+          ? t('email.resubscribe.description', { series: fullSeriesName, email })
+          : t('email.unsubscribe.description', { series: fullSeriesName, email })}
+      </Text>
+
+      <HoliGap size="ml" />
+
+      {!isResubscribed && (
+        <>
+          <Text size="md" color="main" textAlign="left">
+            {t('email.unsubscribe.subText')}
+          </Text>
+
+          <HoliGap size="s" />
+
+          <Pressable onPress={onResubscribe} style={styles.resubscribeButton}>
+            <Text size="lg" color="informative" textAlign="left">
+              {t('email.unsubscribe.button')}
+            </Text>
+            <HoliIcon icon={Refresh} size={24} color={theme.colors.brandB300} />
+          </Pressable>
+
+          <HoliGap size="ml" />
+        </>
+      )}
+
+      <Button size="lg" label="Log into holi" variant="primary" onPress={onLogin} />
+    </Animated.View>
+  )
+}
+
+const styles = StyleSheet.create({
+  resubscribeButton: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: Spacing['4xs'],
+  },
+})
diff --git a/core/screens/mail/components/EmailIllustration.tsx b/core/screens/mail/components/EmailIllustration.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a4d33962bfb33e7bfc4076bfa15fba0975d55004
--- /dev/null
+++ b/core/screens/mail/components/EmailIllustration.tsx
@@ -0,0 +1,41 @@
+import type React from 'react'
+import { Animated, StyleSheet } from 'react-native'
+import HoliLocalImage from '@holi/ui/components/atoms/HoliLocalImage'
+import Spacing from '@holi/ui/foundations/Spacing'
+
+interface EmailIllustrationProps {
+  isResubscribed: boolean
+  fadeAnim?: Animated.Value
+}
+
+export const EmailIllustration: React.FC<EmailIllustrationProps> = ({ isResubscribed, fadeAnim }) => {
+  return (
+    <Animated.View
+      style={[
+        styles.container,
+        {
+          opacity: fadeAnim,
+        },
+      ]}
+    >
+      <HoliLocalImage
+        imageSource={
+          isResubscribed
+            ? require('@holi/ui/assets/img/illustrations/resubscribe_illustration.svg')
+            : require('@holi/ui/assets/img/illustrations/unsubscribe_illustration.svg')
+        }
+        width={74}
+        height={72}
+        label={isResubscribed ? 'Email Resubscribe' : 'Email Unsubscribe'}
+        isSvg
+      />
+    </Animated.View>
+  )
+}
+
+const styles = StyleSheet.create({
+  container: {
+    alignItems: 'flex-start',
+    marginBottom: Spacing['3xs'],
+  },
+})
diff --git a/core/screens/mail/hooks/useEmailSubscriptionAnimation.ts b/core/screens/mail/hooks/useEmailSubscriptionAnimation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..faeca2a4fa3ae1e8a91cf887ce8d1805ca2d2d8f
--- /dev/null
+++ b/core/screens/mail/hooks/useEmailSubscriptionAnimation.ts
@@ -0,0 +1,33 @@
+import { useRef } from 'react'
+import { Animated } from 'react-native'
+
+interface AnimationValues {
+  fadeAnim: Animated.Value
+  animateTransition: (callback: () => void) => void
+}
+
+export const useEmailSubscriptionAnimation = (): AnimationValues => {
+  const fadeAnim = useRef(new Animated.Value(1)).current
+
+  const animateTransition = (callback: () => void) => {
+    // Fade out
+    Animated.timing(fadeAnim, {
+      toValue: 0,
+      duration: 150,
+      useNativeDriver: true,
+    }).start(() => {
+      callback()
+      // Fade in
+      Animated.timing(fadeAnim, {
+        toValue: 1,
+        duration: 150,
+        useNativeDriver: true,
+      }).start()
+    })
+  }
+
+  return {
+    fadeAnim,
+    animateTransition,
+  }
+}
diff --git a/core/screens/mail/mutations.tsx b/core/screens/mail/mutations.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5595c712aab5e6c46ce65f9a59eded0f7946be5b
--- /dev/null
+++ b/core/screens/mail/mutations.tsx
@@ -0,0 +1,9 @@
+import { gql } from '@apollo/client/core'
+
+export const unsubscribeFromNewsletterMutation = gql`
+  mutation UnsubscribeFromNewsletter($token: String!) {
+    unsubscribeFromNewsletter(token: $token) {
+      success
+    }
+  }
+`
diff --git a/core/screens/mail/utils/maskEmail.ts b/core/screens/mail/utils/maskEmail.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a35f9af264a80d6337c938bada41695dfb54bc25
--- /dev/null
+++ b/core/screens/mail/utils/maskEmail.ts
@@ -0,0 +1,26 @@
+/**
+ * Masks an email address for privacy by showing only the first few characters
+ * of the local part while keeping the domain visible.
+ * Examples:
+ * - "john@example.com" -> "joh*@example.com"
+ * - "a@example.com" -> "*@example.com"
+ * - "ab@example.com" -> "a*@example.com"
+ * - "abc@example.com" -> "a**@example.com"
+ * - 'johnDoe@doe.com' -> 'jo****@doe.com'
+ */
+
+export const maskEmail = (email: string): string => {
+  const [localPart, domain] = email.split('@')
+  if (!domain) return email
+
+  // If local part is less than 3 characters, mask at least 1 letter
+  const visiblePart = localPart.slice(0, 3)
+  const maskedLocalPart =
+    localPart.length === 1
+      ? '*'
+      : localPart.length <= 3
+        ? localPart[0] + '*'.repeat(localPart.length - 1)
+        : visiblePart + '*'.repeat(localPart.length - visiblePart.length)
+
+  return `${maskedLocalPart}@${domain}`
+}
diff --git a/packages/ui/assets/img/illustrations/resubscribe_illustration.svg b/packages/ui/assets/img/illustrations/resubscribe_illustration.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6ebbc219fc655c407c0be12168d0eda3672d3b1d
--- /dev/null
+++ b/packages/ui/assets/img/illustrations/resubscribe_illustration.svg
@@ -0,0 +1,13 @@
+<svg width="74" height="72" viewBox="0 0 74 72" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M2 17C2 13.6863 4.68629 11 8 11L64 11C67.3137 11 70 13.6863 70 17V55C70 58.3137 67.3137 61 64 61H8C4.68629 61 2 58.3137 2 55L2 17Z" fill="white" stroke="#262424" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M67 12L39.0995 46.2007C37.4989 48.1627 34.5011 48.1627 32.9005 46.2007L5 12" stroke="#262424" stroke-linecap="round" stroke-linejoin="round"/>
+<circle cx="65" cy="14" r="7" fill="#262424"/>
+<g clip-path="url(#clip0_4001_14343)">
+<path d="M64.064 14.781L68.1881 10.6562L68.6004 11.0685L63.7104 15.9592L61.0584 13.3073L61.4708 12.895L63.3568 14.7811L63.7104 15.1346L64.064 14.781ZM56.083 13.4997C56.083 18.1482 59.8512 21.9163 64.4997 21.9163C69.1482 21.9163 72.9163 18.1482 72.9163 13.4997C72.9163 8.85116 69.1482 5.08301 64.4997 5.08301C59.8512 5.08301 56.083 8.85116 56.083 13.4997Z" fill="#D6FFC7" stroke="#262424"/>
+</g>
+<defs>
+<clipPath id="clip0_4001_14343">
+<rect width="19" height="19" fill="white" transform="translate(55 4)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/packages/ui/assets/img/illustrations/unsubscribe_illustration.svg b/packages/ui/assets/img/illustrations/unsubscribe_illustration.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0932175b05ae4b7644c63adaf9f3e0ad48485150
--- /dev/null
+++ b/packages/ui/assets/img/illustrations/unsubscribe_illustration.svg
@@ -0,0 +1,8 @@
+<svg width="74" height="72" viewBox="0 0 74 72" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M2 17C2 13.6863 4.68629 11 8 11L64 11C67.3137 11 70 13.6863 70 17V55C70 58.3137 67.3137 61 64 61H8C4.68629 61 2 58.3137 2 55L2 17Z" fill="white" stroke="#262424" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M67 12L39.0995 46.2007C37.4989 48.1627 34.5011 48.1627 32.9005 46.2007L5 12" stroke="#262424" stroke-linecap="round" stroke-linejoin="round"/>
+<circle cx="65" cy="14" r="7" fill="#262424"/>
+<circle cx="64.5" cy="13.5" r="9" fill="#D7F3FF" stroke="#262424"/>
+<path d="M61.333 10.333L67.6663 16.6663" stroke="#262424" stroke-linecap="round"/>
+<path d="M67.667 10.333L61.3337 16.6663" stroke="#262424" stroke-linecap="round"/>
+</svg>