diff --git a/.gitignore b/.gitignore
index 08ee14b..595c16c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,7 @@ node_modules
 /public/build
 /public/entry.worker.js
 /build
-server.js
+/server/index.js
 /app/styles/tailwind.css
 
 /.idea
diff --git a/app/config/config.server.ts b/app/config/config.server.ts
index f47526f..f0e8542 100644
--- a/app/config/config.server.ts
+++ b/app/config/config.server.ts
@@ -36,6 +36,7 @@ invariant(
 	typeof process.env.WEB_PUSH_VAPID_PUBLIC_KEY === "string",
 	`Please define the "WEB_PUSH_VAPID_PUBLIC_KEY" environment variable`,
 );
+invariant(typeof process.env.SENTRY_DSN === "string", `Please define the "SENTRY_DSN" environment variable`);
 
 export default {
 	app: {
@@ -54,6 +55,9 @@ export default {
 		url: process.env.REDIS_URL,
 		password: process.env.REDIS_PASSWORD,
 	},
+	sentry: {
+		dsn: process.env.SENTRY_DSN,
+	},
 	twilio: {
 		authToken: process.env.TWILIO_AUTH_TOKEN,
 	},
diff --git a/app/entry.client.tsx b/app/entry.client.tsx
index 13fa3dc..4c7672c 100644
--- a/app/entry.client.tsx
+++ b/app/entry.client.tsx
@@ -1,5 +1,21 @@
 import { hydrate } from "react-dom";
 import { RemixBrowser } from "@remix-run/react";
+import * as Sentry from "@sentry/browser";
+import { Integrations } from "@sentry/tracing";
+
+declare global {
+	interface Window {
+		shellphoneConfig: {
+			sentry: { dsn: string };
+		};
+	}
+}
+
+Sentry.init({
+	dsn: window.shellphoneConfig.sentry.dsn,
+	tracesSampleRate: 1.0,
+	integrations: [new Integrations.BrowserTracing()],
+});
 
 hydrate(<RemixBrowser />, document);
 
diff --git a/app/root.tsx b/app/root.tsx
index c10cc97..e27b06a 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -1,17 +1,48 @@
 import type { FunctionComponent, PropsWithChildren } from "react";
-import type { LinksFunction } from "@remix-run/node";
-import { Link, Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration, useCatch } from "@remix-run/react";
+import { type LinksFunction, type LoaderFunction, json } from "@remix-run/node";
+import {
+	Link,
+	Links,
+	LiveReload,
+	Meta,
+	Outlet,
+	Scripts,
+	ScrollRestoration,
+	useCatch,
+	useLoaderData,
+} from "@remix-run/react";
 
+import config from "~/config/config.server";
 import Logo from "~/features/core/components/logo";
 
 import styles from "./styles/tailwind.css";
 
 export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }];
 
+type LoaderData = {
+	shellphoneConfig: string;
+};
+export const loader: LoaderFunction = () => {
+	return json<LoaderData>({
+		shellphoneConfig: JSON.stringify({
+			sentry: {
+				dsn: config.sentry.dsn,
+			},
+		}),
+	});
+};
+
 export default function App() {
+	const { shellphoneConfig } = useLoaderData<LoaderData>();
 	return (
 		<Document>
 			<Outlet />
+			<script
+				suppressHydrationWarning
+				dangerouslySetInnerHTML={{
+					__html: `window.shellphoneConfig=${shellphoneConfig};`,
+				}}
+			/>
 		</Document>
 	);
 }
diff --git a/app/routes/__app.tsx b/app/routes/__app.tsx
index 5b07879..f8e5d1a 100644
--- a/app/routes/__app.tsx
+++ b/app/routes/__app.tsx
@@ -1,5 +1,6 @@
 import { type LinksFunction, type LoaderFunction, json } from "@remix-run/node";
-import { Outlet, useCatch, useMatches } from "@remix-run/react";
+import { Outlet, useCatch, useLoaderData, useMatches } from "@remix-run/react";
+import * as Sentry from "@sentry/browser";
 
 import serverConfig from "~/config/config.server";
 import { type SessionData, requireLoggedIn } from "~/utils/auth.server";
@@ -10,6 +11,7 @@ import useServiceWorkerRevalidate from "~/features/core/hooks/use-service-worker
 import useDevice from "~/features/phone-calls/hooks/use-device";
 import footerStyles from "~/features/core/components/footer.css";
 import appStyles from "~/styles/app.css";
+import { useEffect } from "react";
 
 export const links: LinksFunction = () => [
 	{ rel: "stylesheet", href: appStyles },
@@ -35,9 +37,14 @@ export const loader: LoaderFunction = async ({ request }) => {
 export default function __App() {
 	useDevice();
 	useServiceWorkerRevalidate();
+	const { sessionData } = useLoaderData<AppLoaderData>();
 	const matches = useMatches();
 	const hideFooter = matches.some((match) => match.handle?.hideFooter === true);
 
+	useEffect(() => {
+		Sentry.setUser(sessionData.user);
+	}, []);
+
 	return (
 		<>
 			<div className="h-full w-full overflow-hidden fixed bg-gray-100">
diff --git a/package-lock.json b/package-lock.json
index 37818c9..dc7333f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,9 @@
 				"@remix-run/express": "1.5.1",
 				"@remix-run/node": "1.5.1",
 				"@remix-run/react": "1.5.1",
+				"@sentry/browser": "7.3.0",
+				"@sentry/node": "7.3.0",
+				"@sentry/tracing": "7.3.0",
 				"@tailwindcss/forms": "0.5.2",
 				"@tailwindcss/line-clamp": "0.4.0",
 				"@tailwindcss/typography": "0.5.2",
@@ -46,6 +49,7 @@
 				"tiny-invariant": "1.2.0",
 				"tslog": "3.3.3",
 				"twilio": "3.77.1",
+				"uuid": "8.3.2",
 				"web-push": "3.5.0",
 				"zod": "3.17.3"
 			},
@@ -66,6 +70,7 @@
 				"@types/react": "18.0.10",
 				"@types/react-dom": "18.0.5",
 				"@types/secure-password": "3.1.1",
+				"@types/uuid": "8.3.4",
 				"@types/web-push": "3.3.2",
 				"@vitejs/plugin-react": "1.3.2",
 				"c8": "7.11.3",
@@ -2533,15 +2538,6 @@
 				"node": ">=0.6"
 			}
 		},
-		"node_modules/@cypress/request/node_modules/uuid": {
-			"version": "8.3.2",
-			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-			"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-			"dev": true,
-			"bin": {
-				"uuid": "dist/bin/uuid"
-			}
-		},
 		"node_modules/@cypress/xvfb": {
 			"version": "1.2.4",
 			"resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz",
@@ -3994,6 +3990,99 @@
 				"url": "https://github.com/fb55/domhandler?sponsor=1"
 			}
 		},
+		"node_modules/@sentry/browser": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.3.0.tgz",
+			"integrity": "sha512-UJMTDbajKNRGrs4ZQNelrPDaATvSZ9uELpPOtPSG6JUvB1BCwGgsgzz55RS0Uqs7B8KhMnDQ0kIn3FMewM4FMg==",
+			"dependencies": {
+				"@sentry/core": "7.3.0",
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"tslib": "^1.9.3"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/@sentry/core": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.3.0.tgz",
+			"integrity": "sha512-EvuWVlYm0F0+BtIEmQiCL31Fw0cfKSwUTmxc99wvouaabpHBr2zCJHRxaXOWzxS705bYBJEQiFDTIHfoOQZMzA==",
+			"dependencies": {
+				"@sentry/hub": "7.3.0",
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"tslib": "^1.9.3"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/@sentry/hub": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-7.3.0.tgz",
+			"integrity": "sha512-0GtTaWf/hoAMoIFY7Ke6eozIbG3FdIPM364sER4SxUQVSklp6AORrV6p82IgWPROK6aj83cPk9Bszgi6RiF/BA==",
+			"dependencies": {
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"tslib": "^1.9.3"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/@sentry/node": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.3.0.tgz",
+			"integrity": "sha512-hPqLQMdpL9MeirtKDCgy0ekptWh58CCUhvcoQ2bYstVBsyMMTNTbiQqF/ClzanrScQ5CEuGWnCbKxrFN/S6cQg==",
+			"dependencies": {
+				"@sentry/core": "7.3.0",
+				"@sentry/hub": "7.3.0",
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"cookie": "^0.4.1",
+				"https-proxy-agent": "^5.0.0",
+				"lru_map": "^0.3.3",
+				"tslib": "^1.9.3"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/@sentry/tracing": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.3.0.tgz",
+			"integrity": "sha512-A+mLEH8jtLkhfyw81EZA1XgI96jh9TIwH9EST3hdfSPgdZQf0A5sV8oVVh/d9Hw7NVb65Va5KhAZDNhcx5QxUA==",
+			"dependencies": {
+				"@sentry/hub": "7.3.0",
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"tslib": "^1.9.3"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/@sentry/types": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.3.0.tgz",
+			"integrity": "sha512-cGkHdh9+uvbFTj65TjWcXuhe6vQiMY+U+N2GE5xCfmZT9hwuouCASViNsbJMpZqvCg+Yi0fasQLZ71rujiRNOA==",
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/@sentry/utils": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.3.0.tgz",
+			"integrity": "sha512-xUP8TBf2p/c6CN8eFQ7Y+xk0IFrJXsph5ScozqNl/2l/Xs8hd2EiYETqgUklphoYD4J2RxvPwMyqBL15QN6wNg==",
+			"dependencies": {
+				"@sentry/types": "7.3.0",
+				"tslib": "^1.9.3"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
 		"node_modules/@sideway/address": {
 			"version": "4.1.4",
 			"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
@@ -4627,6 +4716,12 @@
 			"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
 			"dev": true
 		},
+		"node_modules/@types/uuid": {
+			"version": "8.3.4",
+			"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
+			"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
+			"dev": true
+		},
 		"node_modules/@types/web-push": {
 			"version": "3.3.2",
 			"resolved": "https://registry.npmjs.org/@types/web-push/-/web-push-3.3.2.tgz",
@@ -5463,6 +5558,14 @@
 				"node": ">= 10.0.0"
 			}
 		},
+		"node_modules/aws-sdk/node_modules/uuid": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
+			"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==",
+			"bin": {
+				"uuid": "dist/bin/uuid"
+			}
+		},
 		"node_modules/aws-sign2": {
 			"version": "0.7.0",
 			"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@@ -6094,14 +6197,6 @@
 				"node": ">=6"
 			}
 		},
-		"node_modules/bullmq/node_modules/uuid": {
-			"version": "8.3.2",
-			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-			"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-			"bin": {
-				"uuid": "dist/bin/uuid"
-			}
-		},
 		"node_modules/bytes": {
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@@ -13723,6 +13818,11 @@
 				"node": ">=8"
 			}
 		},
+		"node_modules/lru_map": {
+			"version": "0.3.3",
+			"resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz",
+			"integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ=="
+		},
 		"node_modules/lru-cache": {
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -17677,14 +17777,6 @@
 				"node": ">=10"
 			}
 		},
-		"node_modules/preview-email/node_modules/uuid": {
-			"version": "8.3.2",
-			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-			"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-			"bin": {
-				"uuid": "dist/bin/uuid"
-			}
-		},
 		"node_modules/prisma": {
 			"version": "3.14.0",
 			"resolved": "https://registry.npmjs.org/prisma/-/prisma-3.14.0.tgz",
@@ -18760,14 +18852,6 @@
 				"@remix-run/server-runtime": "^1.0.0"
 			}
 		},
-		"node_modules/remix-auth/node_modules/uuid": {
-			"version": "8.3.2",
-			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-			"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-			"bin": {
-				"uuid": "dist/bin/uuid"
-			}
-		},
 		"node_modules/remix-seo": {
 			"version": "0.1.0",
 			"resolved": "https://registry.npmjs.org/remix-seo/-/remix-seo-0.1.0.tgz",
@@ -18811,14 +18895,6 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
-		"node_modules/remix-utils/node_modules/uuid": {
-			"version": "8.3.2",
-			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-			"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-			"bin": {
-				"uuid": "dist/bin/uuid"
-			}
-		},
 		"node_modules/repeat-element": {
 			"version": "1.1.4",
 			"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
@@ -21849,9 +21925,9 @@
 			}
 		},
 		"node_modules/uuid": {
-			"version": "8.0.0",
-			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
-			"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==",
+			"version": "8.3.2",
+			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+			"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
 			"bin": {
 				"uuid": "dist/bin/uuid"
 			}
@@ -24448,12 +24524,6 @@
 					"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
 					"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
 					"dev": true
-				},
-				"uuid": {
-					"version": "8.3.2",
-					"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-					"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-					"dev": true
 				}
 			}
 		},
@@ -25482,6 +25552,78 @@
 				}
 			}
 		},
+		"@sentry/browser": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.3.0.tgz",
+			"integrity": "sha512-UJMTDbajKNRGrs4ZQNelrPDaATvSZ9uELpPOtPSG6JUvB1BCwGgsgzz55RS0Uqs7B8KhMnDQ0kIn3FMewM4FMg==",
+			"requires": {
+				"@sentry/core": "7.3.0",
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"tslib": "^1.9.3"
+			}
+		},
+		"@sentry/core": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.3.0.tgz",
+			"integrity": "sha512-EvuWVlYm0F0+BtIEmQiCL31Fw0cfKSwUTmxc99wvouaabpHBr2zCJHRxaXOWzxS705bYBJEQiFDTIHfoOQZMzA==",
+			"requires": {
+				"@sentry/hub": "7.3.0",
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"tslib": "^1.9.3"
+			}
+		},
+		"@sentry/hub": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-7.3.0.tgz",
+			"integrity": "sha512-0GtTaWf/hoAMoIFY7Ke6eozIbG3FdIPM364sER4SxUQVSklp6AORrV6p82IgWPROK6aj83cPk9Bszgi6RiF/BA==",
+			"requires": {
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"tslib": "^1.9.3"
+			}
+		},
+		"@sentry/node": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.3.0.tgz",
+			"integrity": "sha512-hPqLQMdpL9MeirtKDCgy0ekptWh58CCUhvcoQ2bYstVBsyMMTNTbiQqF/ClzanrScQ5CEuGWnCbKxrFN/S6cQg==",
+			"requires": {
+				"@sentry/core": "7.3.0",
+				"@sentry/hub": "7.3.0",
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"cookie": "^0.4.1",
+				"https-proxy-agent": "^5.0.0",
+				"lru_map": "^0.3.3",
+				"tslib": "^1.9.3"
+			}
+		},
+		"@sentry/tracing": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.3.0.tgz",
+			"integrity": "sha512-A+mLEH8jtLkhfyw81EZA1XgI96jh9TIwH9EST3hdfSPgdZQf0A5sV8oVVh/d9Hw7NVb65Va5KhAZDNhcx5QxUA==",
+			"requires": {
+				"@sentry/hub": "7.3.0",
+				"@sentry/types": "7.3.0",
+				"@sentry/utils": "7.3.0",
+				"tslib": "^1.9.3"
+			}
+		},
+		"@sentry/types": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.3.0.tgz",
+			"integrity": "sha512-cGkHdh9+uvbFTj65TjWcXuhe6vQiMY+U+N2GE5xCfmZT9hwuouCASViNsbJMpZqvCg+Yi0fasQLZ71rujiRNOA=="
+		},
+		"@sentry/utils": {
+			"version": "7.3.0",
+			"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.3.0.tgz",
+			"integrity": "sha512-xUP8TBf2p/c6CN8eFQ7Y+xk0IFrJXsph5ScozqNl/2l/Xs8hd2EiYETqgUklphoYD4J2RxvPwMyqBL15QN6wNg==",
+			"requires": {
+				"@sentry/types": "7.3.0",
+				"tslib": "^1.9.3"
+			}
+		},
 		"@sideway/address": {
 			"version": "4.1.4",
 			"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
@@ -26054,6 +26196,12 @@
 			"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
 			"dev": true
 		},
+		"@types/uuid": {
+			"version": "8.3.4",
+			"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
+			"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
+			"dev": true
+		},
 		"@types/web-push": {
 			"version": "3.3.2",
 			"resolved": "https://registry.npmjs.org/@types/web-push/-/web-push-3.3.2.tgz",
@@ -26623,6 +26771,13 @@
 				"url": "0.10.3",
 				"uuid": "8.0.0",
 				"xml2js": "0.4.19"
+			},
+			"dependencies": {
+				"uuid": {
+					"version": "8.0.0",
+					"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
+					"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="
+				}
 			}
 		},
 		"aws-sign2": {
@@ -27131,11 +27286,6 @@
 					"version": "2.1.0",
 					"resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
 					"integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="
-				},
-				"uuid": {
-					"version": "8.3.2",
-					"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-					"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
 				}
 			}
 		},
@@ -32775,6 +32925,11 @@
 			"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
 			"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
 		},
+		"lru_map": {
+			"version": "0.3.3",
+			"resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz",
+			"integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ=="
+		},
 		"lru-cache": {
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -35565,13 +35720,6 @@
 				"open": "7",
 				"pug": "^3.0.2",
 				"uuid": "^8.3.2"
-			},
-			"dependencies": {
-				"uuid": {
-					"version": "8.3.2",
-					"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-					"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
-				}
 			}
 		},
 		"prisma": {
@@ -36434,13 +36582,6 @@
 			"integrity": "sha512-VtzkfxeXbnXilupRTZkP40aik4vFSdwwRT96mbq0UBDMqHVRfQ7h9Y51HFrTufHJZEfAdkCopedMVvm0vQYKag==",
 			"requires": {
 				"uuid": "^8.3.2"
-			},
-			"dependencies": {
-				"uuid": {
-					"version": "8.3.2",
-					"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-					"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
-				}
 			}
 		},
 		"remix-auth-form": {
@@ -36475,11 +36616,6 @@
 					"version": "2.13.0",
 					"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.13.0.tgz",
 					"integrity": "sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw=="
-				},
-				"uuid": {
-					"version": "8.3.2",
-					"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-					"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
 				}
 			}
 		},
@@ -38914,9 +39050,9 @@
 			"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
 		},
 		"uuid": {
-			"version": "8.0.0",
-			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
-			"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="
+			"version": "8.3.2",
+			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+			"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
 		},
 		"uvu": {
 			"version": "0.5.3",
diff --git a/package.json b/package.json
index 3bc3d61..2f27570 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
 		"dev:build": "NODE_ENV=development dotenv npm run build:server -- -- --watch",
 		"dev:css": "NODE_ENV=development tailwindcss -i ./styles/tailwind.css -o ./app/styles/tailwind.css --watch",
 		"dev:remix": "NODE_ENV=development remix watch",
-		"dev:server": "NODE_ENV=development dotenv node ./server.js",
+		"dev:server": "NODE_ENV=development dotenv node ./server/index.js",
 		"dev:worker": "NODE_ENV=development npm run build:worker -- --watch",
 		"dev:init": "NODE_ENV=development dotenv run-s build:remix build:server",
 		"dev": "npm run dev:init && run-p dev:build dev:worker dev:css dev:remix dev:server",
@@ -15,7 +15,7 @@
 		"build:remix": "remix build",
 		"build:worker": "node ./scripts/build-worker.js",
 		"build": "NODE_ENV=production run-s build:css build:remix build:worker build:server",
-		"start": "NODE_ENV=production node ./server.js",
+		"start": "NODE_ENV=production node ./server/index.js",
 		"test": "vitest",
 		"test:coverage": "vitest run --coverage",
 		"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
@@ -57,6 +57,9 @@
 		"@remix-run/express": "1.5.1",
 		"@remix-run/node": "1.5.1",
 		"@remix-run/react": "1.5.1",
+		"@sentry/browser": "7.3.0",
+		"@sentry/node": "7.3.0",
+		"@sentry/tracing": "7.3.0",
 		"@tailwindcss/forms": "0.5.2",
 		"@tailwindcss/line-clamp": "0.4.0",
 		"@tailwindcss/typography": "0.5.2",
@@ -89,6 +92,7 @@
 		"tiny-invariant": "1.2.0",
 		"tslog": "3.3.3",
 		"twilio": "3.77.1",
+		"uuid": "8.3.2",
 		"web-push": "3.5.0",
 		"zod": "3.17.3"
 	},
@@ -109,6 +113,7 @@
 		"@types/react": "18.0.10",
 		"@types/react-dom": "18.0.5",
 		"@types/secure-password": "3.1.1",
+		"@types/uuid": "8.3.4",
 		"@types/web-push": "3.3.2",
 		"@vitejs/plugin-react": "1.3.2",
 		"c8": "7.11.3",
diff --git a/scripts/build-server.js b/scripts/build-server.js
index 951309c..a2568e4 100644
--- a/scripts/build-server.js
+++ b/scripts/build-server.js
@@ -9,8 +9,8 @@ const watch = args.includes("--watch");
 esbuild
 	.build({
 		write: true,
-		outfile: path.join(basePath, "server.js"),
-		entryPoints: [path.join(basePath, "server.ts")],
+		outfile: path.join(basePath, "server/index.js"),
+		entryPoints: [path.join(basePath, "server/index.ts")],
 		platform: "node",
 		format: "cjs",
 		bundle: true,
@@ -19,7 +19,7 @@ esbuild
 			{
 				name: "remix-bundle-external",
 				setup(build) {
-					build.onResolve({ filter: /^\.\/build$/ }, () => ({ external: true }));
+					build.onResolve({ filter: /^\.\.\/build$/ }, () => ({ external: true }));
 				},
 			},
 		],
@@ -37,7 +37,7 @@ esbuild
 							process.exit(1);
 						}
 
-						console.log("Server rebuilt successfully");
+						console.log("Server rebuilt successfully"); // TODO: find a way to restart the dev server process
 					},
 			  }
 			: false,
diff --git a/server.ts b/server/index.ts
similarity index 61%
rename from server.ts
rename to server/index.ts
index cecf59f..e86c1b2 100644
--- a/server.ts
+++ b/server/index.ts
@@ -1,18 +1,20 @@
-import path from "node:path";
-import express, { type NextFunction, type Request, type Response } from "express";
+import express from "express";
 import compression from "compression";
 import morgan from "morgan";
 import { createRequestHandler } from "@remix-run/express";
-import { createBullBoard } from "@bull-board/api";
-import { BullMQAdapter } from "@bull-board/api/bullMQAdapter";
-import { ExpressAdapter } from "@bull-board/express";
-import { GlobalRole } from "@prisma/client";
+import * as Sentry from "@sentry/node";
 
-import cronJobs from "~/cron-jobs";
-import queues from "~/queues";
+import config from "~/config/config.server";
 import logger from "~/utils/logger.server";
-import { __getSession } from "~/utils/session.server";
-import { type SessionData } from "~/utils/auth.server";
+import { adminMiddleware, setupBullBoard } from "./queues";
+import { registerSentry, sentryLoadContext } from "./sentry-remix";
+
+Sentry.init({
+	dsn: config.sentry.dsn,
+	integrations: [new Sentry.Integrations.Http({ tracing: true })],
+	tracesSampleRate: 1.0,
+	environment: process.env.NODE_ENV,
+});
 
 const app = express();
 app.use((req, res, next) => {
@@ -83,46 +85,21 @@ app.all("*", (req, res, next) => {
 	}
 
 	return createRequestHandler({
-		build: require("./build"),
+		build: registerSentry(require("../build")),
 		mode: process.env.NODE_ENV,
+		getLoadContext: sentryLoadContext,
 	})(req, res, next);
 });
 
 const port = process.env.PORT || 3000;
 app.listen(port, () => {
-	require("./build"); // preload the build so we're ready for the first request
 	logger.info(`Server listening on port ${port}`);
 });
 
-async function adminMiddleware(req: Request, res: Response, next: NextFunction) {
-	const session = await __getSession(req.headers.cookie);
-	const sessionData: SessionData | undefined = session.data.user;
-	if (!sessionData || sessionData.user.role !== GlobalRole.SUPERADMIN) {
-		return res.setHeader("Location", "/sign-in").status(302).end();
-	}
-
-	next();
-}
-
-function setupBullBoard() {
-	const serverAdapter = new ExpressAdapter();
-	const cronJobsQueues = registerCronJobs();
-	createBullBoard({
-		queues: [...queues, ...cronJobsQueues].map((queue) => new BullMQAdapter(queue)),
-		serverAdapter,
-	});
-	serverAdapter.setBasePath("/admin/queues");
-	return serverAdapter;
-}
-
-function registerCronJobs() {
-	return cronJobs.map((registerCronJob) => registerCronJob());
-}
-
-const buildDir = path.join(process.cwd(), "build");
 function purgeRequireCache() {
+	const resolved = require.resolve("../build");
 	for (const key in require.cache) {
-		if (key.startsWith(buildDir)) {
+		if (key.startsWith(resolved)) {
 			delete require.cache[key];
 		}
 	}
diff --git a/server/queues.ts b/server/queues.ts
new file mode 100644
index 0000000..f4a57e6
--- /dev/null
+++ b/server/queues.ts
@@ -0,0 +1,35 @@
+import type { NextFunction, Request, Response } from "express";
+import { ExpressAdapter } from "@bull-board/express";
+import { BullMQAdapter } from "@bull-board/api/bullMQAdapter";
+import { createBullBoard } from "@bull-board/api";
+import { GlobalRole } from "@prisma/client";
+
+import { __getSession } from "~/utils/session.server";
+import type { SessionData } from "~/utils/auth.server";
+import queues from "~/queues";
+import cronJobs from "~/cron-jobs";
+
+export async function adminMiddleware(req: Request, res: Response, next: NextFunction) {
+	const session = await __getSession(req.headers.cookie);
+	const sessionData: SessionData | undefined = session.data.user;
+	if (!sessionData || sessionData.user.role !== GlobalRole.SUPERADMIN) {
+		return res.setHeader("Location", "/sign-in").status(302).end();
+	}
+
+	next();
+}
+
+export function setupBullBoard() {
+	const serverAdapter = new ExpressAdapter();
+	const cronJobsQueues = registerCronJobs();
+	createBullBoard({
+		queues: [...queues, ...cronJobsQueues].map((queue) => new BullMQAdapter(queue)),
+		serverAdapter,
+	});
+	serverAdapter.setBasePath("/admin/queues");
+	return serverAdapter;
+}
+
+function registerCronJobs() {
+	return cronJobs.map((registerCronJob) => registerCronJob());
+}
diff --git a/server/sentry-remix.ts b/server/sentry-remix.ts
new file mode 100644
index 0000000..78693c6
--- /dev/null
+++ b/server/sentry-remix.ts
@@ -0,0 +1,109 @@
+import type { Request, Response } from "express";
+import type { ActionFunction, DataFunctionArgs, LoaderFunction, ServerBuild } from "@remix-run/node";
+import { isResponse } from "@remix-run/server-runtime/responses";
+import type { Transaction } from "@sentry/types";
+import * as Sentry from "@sentry/node";
+import { v4 as uuid } from "uuid";
+
+import { __getSession } from "~/utils/session.server";
+import type { SessionData } from "~/utils/auth.server";
+
+function wrapDataFunc(func: ActionFunction | LoaderFunction, routeId: string, method: string) {
+	const ogFunc = func;
+
+	return async (args: DataFunctionArgs) => {
+		const session = await __getSession(args.request.headers.get("Cookie"));
+		const sessionData: SessionData | undefined = session.data.user;
+		if (sessionData) {
+			Sentry.setUser({
+				id: sessionData.user.id,
+				email: sessionData.user.email,
+				role: sessionData.user.role,
+			});
+		} else {
+			Sentry.configureScope((scope) => scope.setUser(null));
+		}
+
+		const parentTransaction: Transaction | undefined = args.context && args.context.__sentry_transaction;
+		const transaction = parentTransaction?.startChild({
+			op: `${method}:${routeId}`,
+			description: `${method}: ${routeId}`,
+		});
+		if (transaction) {
+			transaction.setStatus("ok");
+			transaction.transaction = parentTransaction;
+		}
+
+		try {
+			return await ogFunc(args);
+		} catch (error) {
+			if (isResponse(error)) {
+				throw error;
+			}
+
+			Sentry.captureException(error, {
+				tags: {
+					global_id: parentTransaction && parentTransaction.tags["global_id"],
+				},
+			});
+			transaction?.setStatus("internal_error");
+			throw error;
+		} finally {
+			transaction?.finish();
+		}
+	};
+}
+
+// Register Sentry across your entire remix build.
+export function registerSentry(build: ServerBuild) {
+	type Route = ServerBuild["routes"][string];
+
+	const routes: Record<string, Route> = {};
+
+	for (const [id, route] of Object.entries(build.routes)) {
+		const newRoute: Route = { ...route, module: { ...route.module } };
+
+		if (route.module.action) {
+			newRoute.module.action = wrapDataFunc(route.module.action, id, "action");
+		}
+
+		if (route.module.loader) {
+			newRoute.module.loader = wrapDataFunc(route.module.loader, id, "loader");
+		}
+
+		routes[id] = newRoute;
+	}
+
+	return {
+		...build,
+		routes,
+	};
+}
+
+export function sentryLoadContext(req: Request, res: Response) {
+	const transaction = Sentry.getCurrentHub().startTransaction({
+		op: "request",
+		name: `${req.method}: ${req.url}`,
+		description: `${req.method}: ${req.url}`,
+		metadata: {
+			requestPath: req.url,
+		},
+		tags: {
+			global_id: uuid(),
+		},
+	});
+	transaction && transaction.setStatus("internal_error");
+
+	res.once("finish", () => {
+		if (transaction) {
+			transaction.setHttpStatus(res.statusCode);
+			transaction.setTag("http.status_code", res.statusCode);
+			transaction.setTag("http.method", req.method);
+			transaction.finish();
+		}
+	});
+
+	return {
+		__sentry_transaction: transaction,
+	};
+}