diff --git a/config/pmd-suppressions.properties b/config/pmd-suppressions.properties
index c1361e2151f9cb1865eae99ea0a17f72aba74503..11439c609a026b20bde6d2eed37aa7a4777cabd7 100644
--- a/config/pmd-suppressions.properties
+++ b/config/pmd-suppressions.properties
@@ -1,11 +1,13 @@
 # See https://maven.apache.org/plugins/maven-pmd-plugin/examples/violation-exclusions.html
 # annotations generate not clean code
+fr.agrometinfo.www.server.dao.RegionDaoHibernate=UselessOverridingMethod
 fr.agrometinfo.www.shared.dto.ChoiceDTOBeanJsonDeserializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.ChoiceDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.ChoiceDTO_MapperImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonDeserializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.IndicatorDTO_MapperImpl=UnnecessaryImport
+fr.agrometinfo.www.shared.dto.MessageDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonDeserializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.PeriodDTO_MapperImpl=UnnecessaryImport
@@ -14,6 +16,7 @@ fr.agrometinfo.www.shared.dto.SimpleFeatureBeanJsonSerializerImpl=UnnecessaryImp
 fr.agrometinfo.www.shared.dto.SummaryDTOBeanJsonDeserializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.SummaryDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.SummaryDTO_MapperImpl=UnnecessaryImport
+fr.agrometinfo.www.shared.service.ApplicationServiceFactory=UnnecessaryImport
 fr.agrometinfo.www.shared.service.IndicatorServiceFactory=UnnecessaryImport
 org.geojson.FeatureBeanJsonDeserializerImpl=UnnecessaryImport
 org.geojson.FeatureBeanJsonSerializerImpl=UnnecessaryImport
@@ -31,5 +34,3 @@ org.geojson.MultiPolygon_MapperImpl=UnnecessaryImport
 org.geojson.PolygonBeanJsonDeserializerImpl=UnnecessaryImport
 org.geojson.PolygonBeanJsonSerializerImpl=UnnecessaryImport
 org.geojson.Polygon_MapperImpl=UnnecessaryImport
-# wrong positive
-fr.agrometinfo.www.server.dao.RegionDaoHibernate=UselessOverridingMethod
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java
index 238bb3fc04aad5679757a093e93a607ca611021b..d1e39992a45f7808c8dddbb88795f4480e77a2c5 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java
@@ -172,6 +172,12 @@ public interface AppConstants extends com.google.gwt.i18n.client.ConstantsWithLo
     @DefaultStringValue("HTTP status text:")
     String failureStatusText();
 
+    /**
+     * @return translation
+     */
+    @DefaultStringValue("Invalid e-mail address")
+    String invalidEmailAddress();
+
     /**
      * @return translation
      */
@@ -228,6 +234,12 @@ public interface AppConstants extends com.google.gwt.i18n.client.ConstantsWithLo
     @DefaultStringValue("Other AgroClim's services and tools")
     String otherAgroclimApps();
 
+    /**
+     * @return translation
+     */
+    @DefaultStringValue("* This field is required.")
+    String requiredErrorMessage();
+
     /**
      * @return translation
      */
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java
index 4ef2fbb67af7ff9ea85982d5191c599e6dd6773d..f6cbd88b70f0cde3a1fa578ded409bc3d44ca1f6 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java
@@ -59,8 +59,8 @@ public interface AppMessages extends Messages {
      * @param details failure details
      * @return translation
      */
-    @DefaultMessage("Failed to retrieve periods: {0}.")
-    String failureRetrievePeriods(String details);
+    @DefaultMessage("Failed to communicate with server: {0}.")
+    String failureResponse(String details);
 
     /**
      * @param statusCode HTTP status code
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/ContactPresenter.java b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/ContactPresenter.java
index 1b3640d856713243a09493f01b4302c999bb1f30..834a028085b924da1d58124fb575ccca6b945bf9 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/ContactPresenter.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/ContactPresenter.java
@@ -1,7 +1,17 @@
 package fr.agrometinfo.www.client.presenter;
 
+import java.util.StringJoiner;
+
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.Navigator;
+
+import fr.agrometinfo.www.client.util.ApplicationUtils;
+import fr.agrometinfo.www.client.util.UiUtils;
 import fr.agrometinfo.www.client.view.BaseView;
 import fr.agrometinfo.www.client.view.ContactView;
+import fr.agrometinfo.www.client.view.LayoutView;
+import fr.agrometinfo.www.shared.dto.MessageDTO;
+import fr.agrometinfo.www.shared.service.ApplicationServiceFactory;
 
 /**
  * Presenter related to contact form.
@@ -14,12 +24,63 @@ public final class ContactPresenter implements Presenter {
      * Interface for the related view.
      */
     public interface View extends BaseView<ContactPresenter> {
+        /**
+         * Close modal.
+         */
+        void close();
+    }
+
+    /**
+     * The layout handling the panel.
+     */
+    private LayoutView layoutView;
+
+    /**
+     * Related view.
+     */
+    private ContactView view;
+
+    /**
+     * Send an e-mail to AgroMetInfo team.
+     *
+     * @param name         user's name
+     * @param emailAddress user's e-mail address
+     * @param message      message
+     */
+    public void send(final String name, final String emailAddress, final String message) {
+        final StringJoiner sj = new StringJoiner("\n");
+        sj.add(message);
+        sj.add("");
+        sj.add("----");
+        sj.add("URL : " + Window.Location.getHref());
+        sj.add("Application version : " + ApplicationUtils.getVersion());
+        sj.add("Browser : " + UiUtils.getRealBrowserName());
+        sj.add("UserAgent : " + Navigator.getUserAgent());
+        sj.add("OS : " + Navigator.getPlatform());
+        sj.add("Browser size : " + Window.getClientWidth() + "x" + Window.getClientHeight());
+        sj.add("Screen size : " + UiUtils.getScreenWidth() + "x" + UiUtils.getScreenHeight());
 
+        final MessageDTO dto = new MessageDTO();
+        dto.setName(name);
+        dto.setEmailAddress(emailAddress);
+        dto.setMessage(sj.toString());
+        ApplicationServiceFactory.INSTANCE//
+        .sendMessage(dto) //
+        .onSuccess(v -> view.close()) //
+        .onFailed(layoutView::failureNotification) //
+        .send();
+    }
+
+    /**
+     * @param lView the layout handling the panel.
+     */
+    public void setLayoutView(final LayoutView lView) {
+        layoutView = lView;
     }
 
     @Override
     public void start() {
-        final ContactView view = new ContactView();
+        view = new ContactView();
         view.setPresenter(this);
         view.init();
     }
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java
index 868d4e3c08e80233bddb860c028a36108d9353e8..b58c8e14c07f236af38409c4f99bdb2d792aaa14 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java
@@ -170,7 +170,9 @@ public final class LayoutPresenter implements Presenter {
      * Display contact form.
      */
     public void showContactView() {
-        new ContactPresenter().start();
+        final ContactPresenter presenter = new ContactPresenter();
+        presenter.setLayoutView(view);
+        presenter.start();
     }
 
     @Override
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java
index 1b35920f8fb05976a5cf798ad164046e3d2c038c..db182cbc5a43177d8d40f62cd63a57d96d5680f9 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java
@@ -6,6 +6,39 @@ package fr.agrometinfo.www.client.util;
  * @author Olivier Maury
  */
 public final class UiUtils {
+    /**
+     * @return the name of the used browser.
+     */
+    public static native String getBrowserName() /*-{
+        return navigator.userAgent.toLowerCase();
+    }-*/;
+
+    /**
+     * @return browser name, not JavaScript value from navigator.*name.
+     */
+    public static String getRealBrowserName() {
+        if (isChromeBrowser()) {
+            return "Chrome";
+        }
+        if (isFirefoxBrowser()) {
+            return "Firefox";
+        }
+        if (isIEBrowser()) {
+            return "Internet Explorer";
+        }
+        if (isOperaBrowser()) {
+            return "Opera";
+        }
+        return "Unknown";
+    }
+
+    /**
+     * @return screen height (px)
+     */
+    public static native int getScreenHeight() /*-{
+        return screen.height;
+    }-*/;
+
     /**
      * @return screen width (px)
      */
@@ -13,6 +46,35 @@ public final class UiUtils {
         return screen.width;
     }-*/;
 
+    /**
+     * @return true if the current browser is Chrome.
+     */
+    public static boolean isChromeBrowser() {
+        return getBrowserName().toLowerCase().contains("chrome") && !getBrowserName().toLowerCase().contains("opr");
+    }
+
+    /**
+     * @return true if the current browser is Firefox.
+     */
+    public static boolean isFirefoxBrowser() {
+        return getBrowserName().toLowerCase().contains("firefox");
+    }
+
+    /**
+     * @return true if the current browser is IE (Internet Explorer).
+     */
+    public static boolean isIEBrowser() {
+        return getBrowserName().toLowerCase().contains("msie");
+    }
+
+    /**
+     * @return true if the current browser is Opera.
+     */
+    public static boolean isOperaBrowser() {
+        return getBrowserName().toLowerCase().contains("opr");
+    }
+
+
     /**
      * No Constructor.
      */
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/view/ContactView.java b/www-client/src/main/java/fr/agrometinfo/www/client/view/ContactView.java
index 89e620fb6c02bfd7d04411367d55e655ef24c462..d406b65eeb6a0c1649bd9d854c9664cef42219d6 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/view/ContactView.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/view/ContactView.java
@@ -4,14 +4,17 @@ import static org.jboss.elemento.Elements.a;
 import static org.jboss.elemento.Elements.span;
 
 import org.dominokit.domino.ui.button.Button;
+import org.dominokit.domino.ui.forms.EmailBox;
 import org.dominokit.domino.ui.forms.TextArea;
 import org.dominokit.domino.ui.forms.TextBox;
 import org.dominokit.domino.ui.icons.Icons;
 import org.dominokit.domino.ui.modals.ModalDialog;
 import org.dominokit.domino.ui.utils.TextNode;
+import org.jboss.elemento.HtmlContentBuilder;
 
 import com.google.gwt.core.client.GWT;
 
+import elemental2.dom.HTMLAnchorElement;
 import fr.agrometinfo.www.client.i18n.AppConstants;
 import fr.agrometinfo.www.client.presenter.ContactPresenter;
 
@@ -32,45 +35,76 @@ public final class ContactView extends AbstractBaseView<ContactPresenter> implem
      */
     private ModalDialog modal;
 
+    /**
+     * Input box for user's name.
+     */
+    private TextBox nameTextBox;
+
+    /**
+     * Input box for user's e-mail address.
+     */
+    private EmailBox emailBox;
+
+    /**
+     * Input box for user's message.
+     */
+    private TextArea bodyTextBox;
+
+    @Override
+    public void close() {
+        modal.close();
+    }
+
     @Override
     public void init() {
         GWT.log("ContactView.init()");
-        final TextBox nameTextBox = TextBox.create(CSTS.contactNameField())
+        nameTextBox = TextBox.create(CSTS.contactNameField())
                 .setRequired(true)
+                .setRequiredErrorMessage(CSTS.requiredErrorMessage())
                 .setAutoValidation(true)
                 .setFixErrorsPosition(true)
                 .addLeftAddOn(Icons.ALL.label());
-        final TextBox emailTextBox = TextBox.create(CSTS.contactEmailField())
+        emailBox = EmailBox.create(CSTS.contactEmailField())
                 .setRequired(true)
+                .setRequiredErrorMessage(CSTS.requiredErrorMessage())
+                .setTypeMismatchErrorMessage(CSTS.invalidEmailAddress())
                 .setAutoValidation(true)
                 .setFixErrorsPosition(true)
                 .addLeftAddOn(Icons.ALL.email());
-        final TextArea bodyTextBox = TextArea.create(CSTS.contactMessageField()) //
+        bodyTextBox = TextArea.create(CSTS.contactMessageField()) //
                 .setRequired(true)
+                .setRequiredErrorMessage(CSTS.requiredErrorMessage())
                 .setAutoValidation(true)
                 .setFixErrorsPosition(true)
                 .addLeftAddOn(Icons.ALL.textarea_mdi())
                 .setPlaceholder(CSTS.contactMessagePlaceHolder());
+        final HtmlContentBuilder<HTMLAnchorElement> seeFaq = a(".").attr("target", "_blank") //
+                .attr("href", "../explications.html").textContent(CSTS.contactSeeFAQ());
+        final HtmlContentBuilder<HTMLAnchorElement> privacy = a(".").attr("target", "_blank") //
+                .attr("href", "../privacy.html").textContent(CSTS.seePrivacyPolicy());
         modal = ModalDialog.create(CSTS.contactUs())
                 .setAutoClose(true) //
-                .appendChild(a(".").textContent(CSTS.contactSeeFAQ())) //
+                .appendChild(seeFaq) //
                 .appendChild(span().textContent(" ")) //
                 .appendChild(TextNode.of(CSTS.contactSeeFAQ2())) //
                 .appendChild(nameTextBox) //
-                .appendChild(emailTextBox) //
+                .appendChild(emailBox) //
                 .appendChild(bodyTextBox) //
                 .appendChild(TextNode.of(CSTS.contactDataGathering())) //
                 .appendChild(span().textContent(" ")) //
-                .appendChild(a(".").textContent(CSTS.seePrivacyPolicy())) //
+                .appendChild(privacy) //
                 .appendFooterChild(Button.create(CSTS.cancel()).linkify() //
                         .addClickListener(e -> modal.close())) //
                 .appendFooterChild(Button.create(CSTS.contactSendMessage()).linkify() //
-                        .addClickListener(e -> this.onSendClick()) //
-                        ).open();
+                        .addClickListener(e -> this.onSendClick())) //
+                .open();
     }
 
     private void onSendClick() {
         GWT.log("ContactView.onSendClick()");
-        modal.close();
+        if (!nameTextBox.validate().isValid() || !emailBox.validate().isValid() || !bodyTextBox.validate().isValid()) {
+            return;
+        }
+        getPresenter().send(nameTextBox.getValue(), emailBox.getValue(), bodyTextBox.getValue());
     }
 }
diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java b/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java
index c01b1b5f8f7cfb17c9513661d53953103d5c38b8..f93e4d1e24510efe7ac234298d397d73852a0778 100644
--- a/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java
+++ b/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java
@@ -186,7 +186,7 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen
 
     @Override
     public void failureNotification(final FailedResponseBean failedResponse) {
-        this.notification(MSGS.failureRetrievePeriods(getDetails(failedResponse)));
+        this.notification(MSGS.failureResponse(getDetails(failedResponse)));
     }
 
     /**
diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties
index e744828152a53b94eb415758cab3081bb1b308ad..a027dbb4eb1dd4066645a0da69282bb2cc65acdb 100644
--- a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties
+++ b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties
@@ -26,6 +26,7 @@ dailyValues = Valeurs journalières
 failureBody = Corps :
 failureHeaders = Entêtes HTTP :
 failureStatusText = Texte d’état HTTP :
+invalidEmailAddress = Adresse courriel invalide
 login = Se connecter
 loginOrSignIn = ou s’inscrire avec
 logout = Se déconnecter
@@ -35,6 +36,7 @@ no= Non
 normalComparison= Comparaison à la normale
 normalComparisonTooltip= <b>La comparaison à la normale</b> se calcule en soustrayant <b>la moyenne de l’indicateur choisi</b> pour les trente dernières années (1990-2020) de <b>l’année sélectionnée</b>.
 otherAgroclimApps = Autres services et outils d’AgroClim
+requiredErrorMessage = * Ce champ est obligatoire.
 seePrivacyPolicy = Consultez le paragraphe « Données personnelles » dans les mentions légales.
 toggleRightPanel = Afficher / masquer le panneau de droite
 userProfile = Compte et paramètres
diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties
index 354ca34bd3362276c381f3843fa869f7b742ee27..2505c7800fff5cad4721eeb5a45fd494b27e6611 100644
--- a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties
+++ b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties
@@ -4,6 +4,7 @@ averageValue = Valeur moyenne de l’indicateur {0} ({1}) en {2} sur {3}
 cell = Maille n°{0}
 chartSubtitle = {0,date,medium}. Unité : {1}
 comparedValue = Écart moyen de la valeur de l’indicateur {0} ({1}) en {2} sur {3}
+failureResponse = La communication avec le serveur a échoué : {0}
 failureStatusCode = Code d’état HTTP : {0}
 nbOfIndicatorPeriods[\=0] = Aucune période.
 nbOfIndicatorPeriods[\=1] = Une période.
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java b/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java
index 78b496fd3892e260d19091f9622a7c4ec269569f..9e95b3a7f50f990a88bb30955c8767f658199d38 100644
--- a/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java
@@ -62,7 +62,11 @@ public class AgroMetInfoConfiguration {
         /**
          * SMTP password.
          */
-        SMTP_PASSWORD("smtp.password");
+        SMTP_PASSWORD("smtp.password"),
+        /**
+         * E-mail address for support.
+         */
+        SUPPORT_EMAIL("support.email");
 
         /**
          * Key in config.properties.
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationConfig.java b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationConfig.java
index 1e63fd974adb29e0f543910b4aec0fce27a82319..26fef07458cd2a65255a88a869f67a0263d17ced 100644
--- a/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationConfig.java
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationConfig.java
@@ -43,7 +43,7 @@ public class ApplicationConfig extends Application {
                 // Jackson configuration
                 JacksonConfig.class,
                 // JAX-RS resources
-                GeometryResource.class, IndicatorResource.class, UserResource.class,
+                ApplicationResource.class, GeometryResource.class, IndicatorResource.class, UserResource.class,
                 // POJO
                 // Dependencies of resources
                 LogFilter.class
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fdbada1a3ed66d90b6d811b52984e1bdbe77e74
--- /dev/null
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java
@@ -0,0 +1,77 @@
+package fr.agrometinfo.www.server.rs;
+
+import fr.agrometinfo.www.server.exception.AgroMetInfoException;
+import fr.agrometinfo.www.server.service.MailService;
+import fr.agrometinfo.www.server.service.MailService.Mail;
+import fr.agrometinfo.www.server.util.ST;
+import fr.agrometinfo.www.server.util.ST.Key;
+import fr.agrometinfo.www.shared.dto.MessageDTO;
+import fr.agrometinfo.www.shared.service.ApplicationService;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * Resource to handle application end points.
+ *
+ * @author Olivier Maury
+ */
+@Log4j2
+@Path(ApplicationService.PATH)
+@RequestScoped
+public class ApplicationResource implements ApplicationService {
+    /**
+     * E-mail template.
+     */
+    private static final String TEMPLATE = """
+            nom :
+            =====
+            <NAME>
+
+            adresse courriel :
+            ==================
+            <EMAIL>
+
+            message :
+            =========
+            <MESSAGE>
+            """;
+    /**
+     * Convert DTO from "Contact Us" form to Mail for MailService.
+     *
+     * @param message dto to convert
+     * @return Mail for MailService
+     */
+    private static Mail toMail(final MessageDTO message) {
+        final ST st = new ST(TEMPLATE);
+        st.add(Key.EMAIL, message.getEmailAddress());
+        st.add(Key.MESSAGE, message.getMessage());
+        st.add(Key.NAME, message.getName());
+        final Mail mail = new Mail();
+        mail.setContent(st.render());
+        mail.setSubject("Formulaire de contact");
+        return mail;
+    }
+
+    /**
+     * Mail service.
+     */
+    @Inject
+    private MailService mailService;
+
+    @POST
+    @Path(ApplicationService.PATH_SEND_EMAIL)
+    @Override
+    public String sendMessage(final MessageDTO message) {
+        LOGGER.traceEntry();
+        try {
+            mailService.send(toMail(message));
+            return "Ok";
+        } catch (final AgroMetInfoException e) {
+            return e.getLocalizedMessage();
+        }
+    }
+
+}
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/service/MailService.java b/www-server/src/main/java/fr/agrometinfo/www/server/service/MailService.java
index 4378252302bbd9e740b45fb4f5dd0f3d798fffe1..f3f025a93096a0b1717f5f0c1562a41731f4b4cf 100644
--- a/www-server/src/main/java/fr/agrometinfo/www/server/service/MailService.java
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/service/MailService.java
@@ -26,6 +26,8 @@ import fr.agrometinfo.www.server.exception.AgroMetInfoException;
 import fr.agrometinfo.www.server.exception.ErrorCategory;
 import fr.agrometinfo.www.server.exception.ErrorType;
 import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
 import jakarta.mail.Address;
 import jakarta.mail.Authenticator;
 import jakarta.mail.Message;
@@ -39,8 +41,6 @@ import jakarta.mail.internet.MimeMessage;
 import lombok.Data;
 import lombok.Getter;
 import lombok.extern.log4j.Log4j2;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
 
 /**
  * Service to send e-mails.
@@ -99,14 +99,13 @@ public class MailService {
 
         @Override
         public void append(final LogEvent event) {
-            Mail mail = new Mail();
-            mail.setFromAddress("agrometinfo@inrae.fr");
-            mail.setSubject("AgroMetInfo - error log");
+            final Mail mail = new Mail();
+            mail.setSubject("error log");
             mail.setContent(super.toSerializable(event).toString());
             mail.setToAddresses(List.of(configuration.get(ConfigurationKey.LOG_EMAIL)));
             try {
                 send(mail);
-            } catch (AgroMetInfoException e) {
+            } catch (final AgroMetInfoException e) {
                 // do nothing
                 LOGGER.info("Cannot send email: {}", e.getMessage());
             }
@@ -177,7 +176,7 @@ public class MailService {
      */
     @PostConstruct
     public void init() {
-        Properties props = new Properties();
+        final Properties props = new Properties();
         props.put("mail.smtp.auth", "true");
         props.put("mail.smtp.host", configuration.get(ConfigurationKey.SMTP_HOST));
         props.put("mail.smtp.port", configuration.get(ConfigurationKey.SMTP_PORT));
@@ -206,7 +205,7 @@ public class MailService {
         final Appender appender = new MailAppender(null, layout, true);
         appender.start();
         config.addAppender(appender);
-        LoggerConfig loggerConfig = LoggerConfig.newBuilder() //
+        final LoggerConfig loggerConfig = LoggerConfig.newBuilder() //
                 .withAdditivity(false) //
                 .withConfig(config) //
                 .withLevel(Level.ERROR) //
@@ -235,13 +234,13 @@ public class MailService {
         try {
             subject = message.getSubject();
             recipients = toString(message.getAllRecipients());
-        } catch (MessagingException e) {
+        } catch (final MessagingException e) {
             throw new AgroMetInfoException(MailErrorType.DETAILS, e.getMessage());
         }
         try {
             Transport.send(message);
-        } catch (MessagingException e) {
-            if (e instanceof SendFailedException sfe) {
+        } catch (final MessagingException e) {
+            if (e instanceof final SendFailedException sfe) {
                 final Address[] invalid = sfe.getInvalidAddresses();
                 if (invalid != null) {
                     throw new AgroMetInfoException(MailErrorType.SEND_FAILED_INVALID, subject, toString(invalid));
@@ -255,6 +254,22 @@ public class MailService {
         }
     }
 
+    /**
+     * Send an e-mail to log e-mail address at application start.
+     */
+    public void sendApplicationStarted() {
+        final Mail mail = new Mail();
+        mail.setContent("Démarrage de AgroMetInfo-www : " + LocalDateTime.now());
+        mail.setFromAddress(configuration.get(ConfigurationKey.APP_EMAIL));
+        mail.setSubject("Démarrage");
+        mail.setToAddresses(List.of(configuration.get(ConfigurationKey.LOG_EMAIL)));
+        try {
+            send(mail);
+        } catch (final AgroMetInfoException e) {
+            LOGGER.info("failed to send e-mail : {}", e);
+        }
+    }
+
     /**
      * @param mail text message to convert
      * @return jakarta converted message
@@ -264,9 +279,15 @@ public class MailService {
         final String environment = configuration.get(ConfigurationKey.ENVIRONMENT);
         try {
             final Message message = new MimeMessage(session);
+            if (mail.getFromAddress() == null) {
+                mail.setFromAddress(configuration.get(ConfigurationKey.APP_EMAIL));
+            }
             message.setFrom(new InternetAddress(mail.getFromAddress()));
-            List<InternetAddress> to = new ArrayList<>();
-            for (String addr : mail.getToAddresses()) {
+            final List<InternetAddress> to = new ArrayList<>();
+            if (mail.getToAddresses() == null || mail.getToAddresses().isEmpty()) {
+                mail.setToAddresses(List.of(configuration.get(ConfigurationKey.SUPPORT_EMAIL)));
+            }
+            for (final String addr : mail.getToAddresses()) {
                 to.add(new InternetAddress(addr));
             }
             message.setRecipients(Message.RecipientType.TO, to.toArray(InternetAddress[]::new));
@@ -275,10 +296,11 @@ public class MailService {
             } else {
                 message.setSubject("AgroMetInfo " + environment + " : " + mail.getSubject());
             }
-            message.setContent(mail.getContent(), "text/plain; charset=UTF-8");
+            final String content = mail.getContent() + "\n-- \n" + configuration.get(ConfigurationKey.APP_URL);
+            message.setContent(content, "text/plain; charset=UTF-8");
             message.setHeader("X-Environment", environment);
             return message;
-        } catch (MessagingException e) {
+        } catch (final MessagingException e) {
             throw new AgroMetInfoException(MailErrorType.DETAILS, e.getLocalizedMessage());
         }
     }
@@ -289,26 +311,9 @@ public class MailService {
      */
     private String toString(final Address[] addresses) {
         final StringJoiner sj = new StringJoiner(", ");
-        for (Address addr : addresses) {
+        for (final Address addr : addresses) {
             sj.add(addr.toString());
         }
         return sj.toString();
     }
-
-    /**
-     * Send an e-mail to log e-mail address at application start.
-     */
-    public void sendApplicationStarted() {
-        Mail mail = new Mail();
-        mail.setContent("Démarrage de AgroMetInfo-www : " + LocalDateTime.now() + "\n-- \n"
-                + configuration.get(ConfigurationKey.APP_URL));
-        mail.setFromAddress(configuration.get(ConfigurationKey.APP_EMAIL));
-        mail.setSubject("Démarrage");
-        mail.setToAddresses(List.of(configuration.get(ConfigurationKey.LOG_EMAIL)));
-        try {
-            send(mail);
-        } catch (AgroMetInfoException e) {
-            LOGGER.info("failed to send e-mail : {}", e);
-        }
-    }
 }
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/util/ST.java b/www-server/src/main/java/fr/agrometinfo/www/server/util/ST.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb7715b786a0441062634fb7841bda14099bc3f3
--- /dev/null
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/util/ST.java
@@ -0,0 +1,86 @@
+package fr.agrometinfo.www.server.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * String template.
+ *
+ * @author Olivier Maury
+ */
+@RequiredArgsConstructor
+public class ST {
+    /**
+     * Key replacement.
+     */
+    public enum Key {
+        /**
+         * User's name.
+         */
+        NAME,
+        /**
+         * User's e-mail.
+         */
+        EMAIL,
+        /**
+         * User's message.
+         */
+        MESSAGE;
+    }
+
+    /**
+     * The template with string to be replaced.
+     */
+    private final String template;
+
+    /**
+     * The strings to replace.
+     */
+    private final Map<String, String> replacements = new HashMap<>();
+
+    /**
+     * Inject an attribute (name/value pair).
+     *
+     * Return {@code this} so we can chain:<br>
+     * {@code t.add("x", 1).add("y", "hi")}
+     *
+     * @param key   key (without &lt; &gt;)
+     * @param value value to replace
+     * @return instance
+     */
+    public ST add(final Key key, final Object value) {
+        replacements.put(key.name(), String.valueOf(value));
+        return this;
+    }
+
+    /**
+     * Inject an attribute (name/value pair).
+     *
+     * Return {@code this} so we can chain:<br>
+     * {@code t.add("x", 1).add("y", "hi")}
+     *
+     * @param key key (without &lt; &gt;)
+     * @param value value to replace
+     * @return instance
+     */
+    public ST add(final String key, final Object value) {
+        replacements.put(key, String.valueOf(value));
+        return this;
+    }
+
+    /**
+     * Render the template with replaced strings.
+     *
+     * @return rendered template
+     */
+    public String render() {
+        String replaced = template;
+        for (final Entry<String, String> entry : replacements.entrySet()) {
+            replaced = replaced.replace("<" + entry.getKey() + ">", entry.getValue());
+        }
+        return replaced;
+    }
+}
diff --git a/www-server/src/main/tomcat10xconf/context.xml b/www-server/src/main/tomcat10xconf/context.xml
index 197593d809e7121ac017bb17a6b1a607ab8d0080..29874ece5ae94d1a17d751845c4aebba83d05866 100644
--- a/www-server/src/main/tomcat10xconf/context.xml
+++ b/www-server/src/main/tomcat10xconf/context.xml
@@ -10,6 +10,7 @@
   <Parameter name="agrometinfo.smtp.port" value="587" />
   <Parameter name="agrometinfo.smtp.user" value="agrometinfoXXXX" />
   <Parameter name="agrometinfo.smtp.password" value="XXXX" />
+  <Parameter name="agrometinfo.support.email" value="agrometinfoXXXX@inrae.fr" />
   <Parameter name="sava.key" value="XXXX" />
   <Parameter name="sava.pass" value="XXXX" />
   <Resource
diff --git a/www-shared/config/pmd-suppressions.properties b/www-shared/config/pmd-suppressions.properties
index a9ac651a732b9d4b82e53b40c3e2b905659b7cc8..35388de1e41ddfd8384f40555527bb8d19c78f1b 100644
--- a/www-shared/config/pmd-suppressions.properties
+++ b/www-shared/config/pmd-suppressions.properties
@@ -6,6 +6,7 @@ fr.agrometinfo.www.shared.dto.ChoiceDTO_MapperImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonDeserializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.IndicatorDTO_MapperImpl=UnnecessaryImport
+fr.agrometinfo.www.shared.dto.MessageDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonDeserializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.PeriodDTO_MapperImpl=UnnecessaryImport
@@ -14,6 +15,7 @@ fr.agrometinfo.www.shared.dto.SimpleFeatureBeanJsonSerializerImpl=UnnecessaryImp
 fr.agrometinfo.www.shared.dto.SummaryDTOBeanJsonDeserializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.SummaryDTOBeanJsonSerializerImpl=UnnecessaryImport
 fr.agrometinfo.www.shared.dto.SummaryDTO_MapperImpl=UnnecessaryImport
+fr.agrometinfo.www.shared.service.ApplicationServiceFactory=UnnecessaryImport
 fr.agrometinfo.www.shared.service.IndicatorServiceFactory=UnnecessaryImport
 org.geojson.FeatureBeanJsonDeserializerImpl=UnnecessaryImport
 org.geojson.FeatureBeanJsonSerializerImpl=UnnecessaryImport
diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/MessageDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/MessageDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..12253ed084ed9cd26ddd7056bf3f715d0d54a5dc
--- /dev/null
+++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/MessageDTO.java
@@ -0,0 +1,65 @@
+package fr.agrometinfo.www.shared.dto;
+
+/**
+ * E-mail message from "Contact us" form.
+ *
+ * @author Olivier Maury
+ */
+public class MessageDTO {
+    /**
+     * User's e-mail address.
+     */
+    private String emailAddress;
+
+    /**
+     * Message.
+     */
+    private String message;
+
+    /**
+     * User's name.
+     */
+    private String name;
+
+    /**
+     * @return user's e-mail address
+     */
+    public final String getEmailAddress() {
+        return emailAddress;
+    }
+
+    /**
+     * @return the message
+     */
+    public final String getMessage() {
+        return message;
+    }
+
+    /**
+     * @return user's name
+     */
+    public final String getName() {
+        return name;
+    }
+
+    /**
+     * @param value user's e-mail address
+     */
+    public final void setEmailAddress(final String value) {
+        this.emailAddress = value;
+    }
+
+    /**
+     * @param value message
+     */
+    public final void setMessage(final String value) {
+        this.message = value;
+    }
+
+    /**
+     * @param value user's name
+     */
+    public final void setName(final String value) {
+        this.name = value;
+    }
+}
diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/service/ApplicationService.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/service/ApplicationService.java
new file mode 100644
index 0000000000000000000000000000000000000000..0082e1c85ca25870d3d858b7a9e13790e7f0b34b
--- /dev/null
+++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/service/ApplicationService.java
@@ -0,0 +1,38 @@
+package fr.agrometinfo.www.shared.service;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+
+import org.dominokit.rest.shared.request.service.annotations.RequestBody;
+import org.dominokit.rest.shared.request.service.annotations.RequestFactory;
+
+import fr.agrometinfo.www.shared.dto.MessageDTO;
+
+/**
+ * Interface for client and server resource.
+ *
+ * @author Olivier Maury
+ */
+@RequestFactory
+@Path(ApplicationService.PATH)
+public interface ApplicationService {
+    /**
+     * Service base path.
+     */
+    String PATH = "application";
+
+    /**
+     * Path for {@link ApplicationService#send()}.
+     */
+    String PATH_SEND_EMAIL = "send";
+
+    /**
+     * Send an e-mail message to AgroMetInfo team.
+     *
+     * @param message      message
+     * @return nothing
+     */
+    @POST
+    @Path(PATH_SEND_EMAIL)
+    String sendMessage(@RequestBody MessageDTO message);
+}