diff --git a/CHANGELOG.md b/CHANGELOG.md
index 163d03d31..90e0882f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,10 @@ and this project adheres to
## [Unreleased]
+### Added
+
+- Add a link preview modal : instead of opening the link directly when clicked on in an email, a modal now shows the true URL and asks to confirm.
+
## [0.5.0] - 2026-03-16
### Added
diff --git a/src/frontend/public/locales/common/en-US.json b/src/frontend/public/locales/common/en-US.json
index 3c36d6480..21be10743 100755
--- a/src/frontend/public/locales/common/en-US.json
+++ b/src/frontend/public/locales/common/en-US.json
@@ -150,6 +150,7 @@
"Back": "Back",
"Back to your inbox": "Back to your inbox",
"BCC: ": "BCC: ",
+ "Be careful!": "Be careful!",
"Blind copy: ": "Blind copy: ",
"Calendar invite": "Calendar invite",
"Cancel": "Cancel",
@@ -432,6 +433,7 @@
"On going": "On going",
"Open {{driveAppName}} preview": "Open {{driveAppName}} preview",
"Open filters": "Open filters",
+ "Open the link": "Open the link",
"Open the menu": "Open the menu",
"Or": "Or",
"or drag and drop some files": "or drag and drop some files",
@@ -442,6 +444,7 @@
"Password reset successfully!": "Password reset successfully!",
"Personal mailbox": "Personal mailbox",
"Personal mailboxes cannot be created when identity synchronization is disabled.": "Personal mailboxes cannot be created when identity synchronization is disabled.",
+ "phishing_notice": "Be careful when clicking links in email, it could be a <1> phishing attempt1>.",
"Please enter a valid email address.": "Please enter a valid email address.",
"Prefix can only contain letters, numbers, dots, underscores and hyphens.": "Prefix can only contain letters, numbers, dots, underscores and hyphens.",
"Prefix is required.": "Prefix is required.",
@@ -553,6 +556,7 @@
"The email {{email}} is invalid.": "The email {{email}} is invalid.",
"The email address is invalid.": "The email address is invalid.",
"The forced signature will be the only one usable for new messages.": "The forced signature will be the only one usable for new messages.",
+ "The link you clicked is probably unsafe :": "The link you clicked is probably unsafe :",
"The message could not be sent.": "The message could not be sent.",
"The message could not be sent. Please try again later.": "The message could not be sent. Please try again later.",
"The personal mailbox {{mailboxAddress}} has been created successfully.": "The personal mailbox <1>{{mailboxAddress}}1> has been created successfully.",
@@ -567,6 +571,7 @@
"This email prefix is not allowed for personal mailboxes. Please choose a different prefix.": "This email prefix is not allowed for personal mailboxes. Please choose a different prefix.",
"This event has been cancelled": "This event has been cancelled",
"This is the only admin of this mailbox, you cannot therefore modify its access.": "This is the only admin of this mailbox, you cannot therefore modify its access.",
+ "This links redirects to :": "This links redirects to :",
"This message has {{count}} attachments_one": "This message has one attachment",
"This message has {{count}} attachments_other": "This message has {{count}} attachments",
"This message has a draft": "This message has a draft",
@@ -645,4 +650,4 @@
"Your email...": "Your email...",
"Your messages have been imported successfully!": "Your messages have been imported successfully!",
"Your session has expired. Please log in again.": "Your session has expired. Please log in again."
-}
+}
\ No newline at end of file
diff --git a/src/frontend/public/locales/common/fr-FR.json b/src/frontend/public/locales/common/fr-FR.json
index e28827ae0..7719738eb 100755
--- a/src/frontend/public/locales/common/fr-FR.json
+++ b/src/frontend/public/locales/common/fr-FR.json
@@ -192,6 +192,7 @@
"Back": "Précédent",
"Back to your inbox": "Retour à votre messagerie",
"BCC: ": "CCI : ",
+ "Be careful!": "Attention !",
"Blind copy: ": "Copie cachée : ",
"Calendar invite": "Invitation calendrier",
"Cancel": "Annuler",
@@ -479,6 +480,7 @@
"On going": "En cours",
"Open {{driveAppName}} preview": "Ouvrir l'aperçu dans {{driveAppName}}",
"Open filters": "Ouvrir les filtres",
+ "Open the link": "Ouvrir le lien",
"Open the menu": "Ouvrir le menu",
"Or": "Ou",
"or drag and drop some files": "ou glissez-déposez des fichiers",
@@ -489,6 +491,7 @@
"Password reset successfully!": "Mot de passe réinitialisé avec succès !",
"Personal mailbox": "Boîte personnelle",
"Personal mailboxes cannot be created when identity synchronization is disabled.": "Les boîtes aux lettres personnelles ne peuvent pas être créées lorsque la synchronisation d'identité est désactivée.",
+ "phishing_notice": "Soyez prudent en cliquant sur les liens dans les emails, il pourrait s'agir d'une <1> tentative de phishing1>",
"Please enter a valid email address.": "Veuillez saisir une adresse email valide.",
"Prefix can only contain letters, numbers, dots, underscores and hyphens.": "Le préfixe ne peut contenir que des lettres, chiffres, points, tirets bas et tirets.",
"Prefix is required.": "Le préfixe est requis.",
@@ -603,6 +606,7 @@
"The email {{email}} is invalid.": "Le courriel {{email}} est invalide.",
"The email address is invalid.": "L'adresse email est invalide.",
"The forced signature will be the only one usable for new messages.": "La signature forcée sera la seule utilisable pour les nouveaux messages.",
+ "The link you clicked is probably unsafe :": "Le lien sur lequel vous avez cliqué est probablement dangereux :",
"The message could not be sent.": "Le message n'a pas pu être envoyé.",
"The message could not be sent. Please try again later.": "Le message n'a pas pu être envoyé. Veuillez réessayer plus tard.",
"The personal mailbox {{mailboxAddress}} has been created successfully.": "L'adresse personnelle {{mailboxAddress}} a été créée avec succès.",
@@ -617,6 +621,7 @@
"This email prefix is not allowed for personal mailboxes. Please choose a different prefix.": "Ce préfixe d'adresse n'est pas autorisé pour les boîtes aux lettres personnelles. Veuillez choisir un autre préfixe.",
"This event has been cancelled": "Cet événement a été annulé",
"This is the only admin of this mailbox, you cannot therefore modify its access.": "C'est le seul administrateur de cette boîte aux lettres, vous ne pouvez donc pas modifier son accès.",
+ "This links redirects to :": "Ce lien redirige vers :",
"This message has {{count}} attachments_one": "Ce message a une pièce jointe",
"This message has {{count}} attachments_many": "Ce message a {{count}} pièces jointes",
"This message has {{count}} attachments_other": "Ce message a {{count}} pièces jointes",
@@ -688,6 +693,7 @@
"You can now inform the person that their mailbox is ready to be used and communicate the instructions for authentication.": "Vous pouvez désormais prévenir la personne que sa boîte aux lettres est prête à être utilisée et lui communiquer les instructions pour s'authentifier.",
"You cannot delete the last editor of this thread": "Vous ne pouvez pas supprimer le dernier éditeur de cette conversation",
"You cannot modify it.": "Vous ne pouvez pas la modifier.",
+ "You clicked on the link \"{{linkText}}\" which redirects to :": "Vous avez cliqué sur le lien \"{{linkText}}\" qui redirige vers :",
"You have {{count}} recipients, which exceeds the maximum of {{max}} recipients per message. The message cannot be sent until you reduce the number of recipients._one": "Vous avez {{count}} destinataire, ce qui dépasse le maximum de {{max}} destinataires autorisés par message. Le message ne peut pas être envoyé tant que vous n'avez pas réduit le nombre de destinataires.",
"You have {{count}} recipients, which exceeds the maximum of {{max}} recipients per message. The message cannot be sent until you reduce the number of recipients._many": "Vous avez {{count}} destinataires, ce qui dépasse le maximum de {{max}} destinataires autorisés par message. Le message ne peut pas être envoyé tant que vous n'avez pas réduit le nombre de destinataires.",
"You have {{count}} recipients, which exceeds the maximum of {{max}} recipients per message. The message cannot be sent until you reduce the number of recipients._other": "Vous avez {{count}} destinataires, ce qui dépasse le maximum de {{max}} destinataires autorisés par message. Le message ne peut pas être envoyé tant que vous n'avez pas réduit le nombre de destinataires.",
@@ -697,4 +703,4 @@
"Your email...": "Renseigner votre email...",
"Your messages have been imported successfully!": "Vos messages ont été importés avec succès !",
"Your session has expired. Please log in again.": "Votre session a expiré. Veuillez vous reconnecter."
-}
+}
\ No newline at end of file
diff --git a/src/frontend/src/features/layouts/components/thread-view/_index.scss b/src/frontend/src/features/layouts/components/thread-view/_index.scss
index b08728182..0a5015817 100644
--- a/src/frontend/src/features/layouts/components/thread-view/_index.scss
+++ b/src/frontend/src/features/layouts/components/thread-view/_index.scss
@@ -146,3 +146,19 @@
top: 0;
z-index: 2;
}
+
+.c__modal__title-icon .material-icons {
+ font-size: var(--icon-size);
+}
+
+.link-preview__children {
+ display: flex;
+ flex-direction: column;
+ gap: var(--c--globals--spacings--xs);
+}
+
+.link-preview__phishing-notice {
+ font-size: var(--c--globals--font--sizes--xs);
+ color: var(--c--contextuals--content--semantic--neutral--secondary);
+ text-align: left;
+}
\ No newline at end of file
diff --git a/src/frontend/src/features/layouts/components/thread-view/components/thread-message/link-preview-modal.tsx b/src/frontend/src/features/layouts/components/thread-view/components/thread-message/link-preview-modal.tsx
new file mode 100644
index 000000000..f84f23c13
--- /dev/null
+++ b/src/frontend/src/features/layouts/components/thread-view/components/thread-message/link-preview-modal.tsx
@@ -0,0 +1,127 @@
+import { useState, useCallback, useRef } from "react";
+import i18n from "@/features/i18n/initI18n";
+import { Button, Modal, ModalSize, Alert, VariantType, iconFromType } from "@gouvfr-lasuite/cunningham-react";
+import classNames from "classnames";
+import { Trans, useTranslation } from "react-i18next";
+/**
+ * Modal component to show a preview of a link.
+ * {linkText}
+ *
+ * @param isOpen - Whether the modal is open
+ * @param onClose - Function to call when the modal is closed
+ * @param url - The URL to preview
+ * @param linkText - The text of the link (optional)
+ * @param hardWarning - Whether to show a more prominent warning
+ * @param decision - Function to call with the user's confirmation choice
+ */
+type LinkPreviewModalProps = {
+ isOpen: boolean;
+ url: string;
+ hardWarning?: boolean;
+ decision: (choice: boolean) => void;
+}
+
+/**
+ * Confirmation modal before redirecting to an external link.
+ * It alerts the user about potential risks (phishing, etc.).
+ */
+export const LinkPreviewModal = ({ isOpen, url, hardWarning, decision }: LinkPreviewModalProps) => {
+ const { t } = useTranslation();
+ return (
+ {
+ hardWarning
+ ? i18n.t('Be careful!')
+ : i18n.t('This links redirects to :')
+ }
+ )}
+ titleIcon={hardWarning && (
+
+ {iconFromType(VariantType.WARNING)}
+
+ )}
+ hideCloseButton={true}
+ actions={[
+ ,
+
+ ]}
+ onClose={() => decision(false)}
+ closeOnClickOutside={true}
+ >
+
+ {hardWarning && i18n.t('The link you clicked is probably unsafe :')}
+ {url}
+