diff --git a/src/lib/components/BudgetConnexionModal.svelte b/src/lib/components/BudgetConnexionModal.svelte
index f41b9775c18bc687ad378c0328b0549fb3ec1d79..d6e50f515f6d26a935b8e490a75f267f70357ee3 100644
--- a/src/lib/components/BudgetConnexionModal.svelte
+++ b/src/lib/components/BudgetConnexionModal.svelte
@@ -10,14 +10,20 @@
   import { browser } from "$app/environment"
   import { goto } from "$app/navigation"
   import { page } from "$app/stores"
+  import type { DisplayMode } from "$lib/displays"
   import {
     trackBudgetPublicSimulation,
     trackBudgetSignInButton,
   } from "$lib/matomo"
+  import type { ParametricReform } from "$lib/reforms"
   import type { CachedSimulation } from "$lib/simulations"
 
+  export let displayMode: DisplayMode
   export let isOpen = false
 
+  const budgetSimulation = getContext("budgetSimulation") as Writable<
+    BudgetSimulation | undefined
+  >
   let cachedSimulations: CachedSimulation[] = []
 
   $: if (browser && isOpen) {
@@ -39,6 +45,36 @@
   function onClose() {
     isOpen = false
   }
+
+  async function sendSimulationRequest() {
+      const urlString = "/budget/demande"
+      const res = await fetch(urlString, {
+        body: JSON.stringify({
+          displayMode,
+          parametricReform: Object.entries($parametricReform)
+            .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
+            .reduce((filtered: ParametricReform, [parameterName, value]) => {
+              if (budgetEditableParametersName.has(parameterName)) {
+                filtered[parameterName] = value
+              }
+              return filtered
+            }, {}),
+        }),
+        headers: {
+          Accept: "application/json",
+          "Content-Type": "application/json; charset=utf-8",
+        },
+        method: "POST",
+      })
+      if (!res.ok) {
+        console.error(
+          `Error ${
+            res.status
+          } while sending a simulation request at ${urlString}\n\n${await res.text()}`,
+        )
+        return
+      }
+  }
 </script>
 
 <Transition appear show={isOpen}>
@@ -154,8 +190,34 @@
                 <div
                   class="flex md:flex-row flex-col w-full px-0 md:px-10 items-center gap-5"
                 >
+==== BASE ====
+                  Voir la simulation<iconify-icon
+                    class="align-[-0.25rem] text-xl"
+                    icon="ri-arrow-right-line"
+                  />
+                </a>
+              </div>
+            {/if}
+
+            <div class="flex flex-col gap-10 bg-gray-100 p-5 border-t mt-10">
+              {#if currentSimulationCache === undefined}
+                <div>
+                  <h2 class="w-full text-left text-2xl font-bold">
+                    Demandez le calcul de votre réforme au service
+                    LexImpact&nbsp;:
+                  </h2>
+                  <p class="w-full text-base font-normal leading-6 mt-1.5 mb-5">
+                    Après vérification par nos services, si elle est calculable
+                    avec les données dont nous disposons et répond au secret
+                    statistique, la simulation sera rendue publique. Vous serez
+                    alors informé par e-mail :
+                  </p>
+                  <span class="font-bold text-sm py-2 pl-10"
+                    >Votre adresse e-mail :</span
+                  >
                   <div
-                    class="flex rounded-t-md border-b-2 border-b-black bg-white px-2 grow max-w-lg"
+                    class="flex md:flex-row flex-col w-full px-0 md:px-10 items-center gap-5"
+==== BASE ====
                   >
                     <input
                       autocomplete="off"
@@ -178,8 +240,25 @@
                         class="ml-2 align-[-0.25rem] text-xl"
                         icon="ri-send-plane-2-line"
                       />
-                    </a>
+==== BASE ====
+                    </div>
+                    <div>
+                      <a
+                        class="flex items-center gap-2 py-2 px-5 shadow-lg bg-white hover:bg-gray-100 active:bg-gray-200 rounded-md border-2 border-le-bleu text-le-bleu text-sm font-bold tracking-[0.085em] uppercase"
+                        data-sveltekit-reload
+                        href={`/auth/login?redirect=${encodeURIComponent(
+                          $page.url.toString(),
+                        )}`}
+                        title="Envoyer votre réforme budgétaire avec cet e-mail"
+                      >
+                        Demander&nbsp;le&nbsp;calcul <iconify-icon
+                          class="ml-2 align-[-0.25rem] text-xl"
+                          icon="ri-send-plane-2-line"
+                        />
+                      </a>
+                    </div>
                   </div>
+==== BASE ====
                 </div>
               </div>
               {#if cachedSimulations !== undefined && cachedSimulations.length > 0}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index ebd94e0feb44ebf11fc3faf04b2c96072a2399ab..1c1a4276f00e9345c9eaab186bfff8bfe613000f 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1762,7 +1762,10 @@
       </div>
     </div>
 
-    <BudgetConnexionModal bind:isOpen={isBudgetConnexionModalOpen} />
+    <BudgetConnexionModal
+      bind:isOpen={isBudgetConnexionModalOpen}
+      {displayMode}
+    />
   {/if}
 
   <!--Bouton flottant "simulation publique" -->
diff --git a/src/routes/budget/demande/+server.ts b/src/routes/budget/demande/+server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7bf1f5c03c0ba22db6a59911f55ea8bdeae288c6
--- /dev/null
+++ b/src/routes/budget/demande/+server.ts
@@ -0,0 +1,103 @@
+import { auditRequire, cleanAudit, type Audit } from "@auditors/core"
+import { error, json } from "@sveltejs/kit"
+import fs from "fs-extra"
+import path from "path"
+
+import { getParameter, rootParameter } from "$lib/parameters"
+import type { ParametricReform } from "$lib/reforms"
+import config from "$lib/server/config"
+import type { CachedSimulation } from "$lib/simulations"
+
+import type { RequestHandler } from "./$types"
+import { hashObject } from "$lib/hash"
+
+const { simulationsBudgetDir } = config
+
+function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
+  if (dataUnknown == null) {
+    return [dataUnknown, null]
+  }
+  if (typeof dataUnknown !== "object") {
+    return audit.unexpectedType(dataUnknown, "object")
+  }
+
+  const data = { ...dataUnknown }
+  const errors: { [key: string]: unknown } = {}
+  const remainingKeys = new Set(Object.keys(data))
+
+  audit.attribute(
+    data,
+    "displayMode",
+    true,
+    errors,
+    remainingKeys,
+    // TODO
+    auditRequire,
+  )
+  audit.attribute(
+    data,
+    "parametricReform",
+    true,
+    errors,
+    remainingKeys,
+    // TODO
+    auditRequire,
+  )
+
+  return audit.reduceRemaining(data, errors, remainingKeys)
+}
+
+export const POST: RequestHandler = async ({ request, url }) => {
+  const [body, bodyError] = auditBody(cleanAudit, await request.json())
+  if (bodyError !== null) {
+    console.error(
+      `Error in ${url.pathname} body:\n${JSON.stringify(
+        body,
+        null,
+        2,
+      )}\n\nError:\n${JSON.stringify(bodyError, null, 2)}`,
+    )
+    throw error(400, `Invalid body: ${JSON.stringify(bodyError, null, 2)}`)
+  }
+
+  const modifiedParametersTitles = Object.keys(
+    (body as { parametricReform: ParametricReform }).parametricReform,
+  ).map((parameterName) => getParameter(rootParameter, parameterName)?.title)
+
+  const modifiedParametersNames = Object.keys(
+    (body as { parametricReform: ParametricReform }).parametricReform,
+  ).map((parameterName) => getParameter(rootParameter, parameterName)?.name)
+
+  const bodyJson = JSON.stringify(body, null, 2)
+
+  const digest = hashObject(
+    (body as { parametricReform: ParametricReform }).parametricReform,
+  )
+
+  const simulationDir = path.join(simulationsBudgetDir, digest.substring(0, 2))
+  const simulationFilePath = path.join(simulationDir, `${digest}.json`)
+  if (!(await fs.pathExists(simulationFilePath))) {
+    await fs.ensureDir(simulationDir)
+    await fs.writeFile(simulationFilePath, bodyJson)
+  }
+
+  const indexDir = path.join(simulationsBudgetDir, "index.json")
+  const indexContents: CachedSimulation[] = (await fs.pathExists(indexDir))
+    ? await fs.readJson(indexDir)
+    : []
+  const contents: CachedSimulation[] = [
+    ...indexContents,
+    {
+      date: new Intl.DateTimeFormat("fr-FR").format(new Date()),
+      hash: digest,
+      parameters: modifiedParametersNames,
+      title: modifiedParametersTitles.join(" | "),
+    } as CachedSimulation,
+  ].filter(
+    (value, index, self) =>
+      index === self.findLastIndex((el) => el.hash === value.hash),
+  )
+  await fs.writeFile(indexDir, JSON.stringify(contents))
+
+  return json({ token: digest })
+}