diff --git a/Cargo.lock b/Cargo.lock
index 4dd813a..6b18ebc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -331,6 +331,54 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "anstream"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "anyhow"
 version = "1.0.71"
@@ -668,7 +716,17 @@ dependencies = [
  "num-traits",
  "serde",
  "wasm-bindgen",
- "windows-targets",
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "chumsky"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d"
+dependencies = [
+ "hashbrown 0.12.3",
+ "stacker",
 ]
 
 [[package]]
@@ -723,6 +781,12 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271"
 
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
 [[package]]
 name = "config"
 version = "0.13.3"
@@ -817,6 +881,16 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.4"
@@ -1077,6 +1151,22 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "email-encoding"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75"
+dependencies = [
+ "base64 0.21.2",
+ "memchr",
+]
+
+[[package]]
+name = "email_address"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112"
+
 [[package]]
 name = "encoding_rs"
 version = "0.8.32"
@@ -1086,17 +1176,27 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "env_filter"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
+dependencies = [
+ "log",
+ "regex",
+]
+
 [[package]]
 name = "env_logger"
-version = "0.10.1"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
+checksum = "05e7cf40684ae96ade6232ed84582f40ce0a66efcd43a5117aef610534f8e0b8"
 dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
  "humantime",
- "is-terminal",
  "log",
- "regex",
- "termcolor",
 ]
 
 [[package]]
@@ -1113,7 +1213,7 @@ checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
 dependencies = [
  "errno-dragonfly",
  "libc",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -1134,7 +1234,7 @@ checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
 dependencies = [
  "cfg-if",
  "home",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -1177,6 +1277,21 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
 [[package]]
 name = "form_urlencoded"
 version = "1.2.0"
@@ -1493,12 +1608,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "hermit-abi"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
-
 [[package]]
 name = "hex"
 version = "0.4.3"
@@ -1539,7 +1648,18 @@ version = "0.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
 dependencies = [
- "windows-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "hostname"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
+dependencies = [
+ "libc",
+ "match_cfg",
+ "winapi",
 ]
 
 [[package]]
@@ -1636,6 +1756,16 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
 [[package]]
 name = "if_chain"
 version = "1.0.2"
@@ -1692,17 +1822,6 @@ version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a53088c87cf71c9d4f3372a2cb9eea1e7b8a0b1bf8b7f7d23fe5b76dbb07e63b"
 
-[[package]]
-name = "is-terminal"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
-dependencies = [
- "hermit-abi 0.3.3",
- "rustix",
- "windows-sys",
-]
-
 [[package]]
 name = "itertools"
 version = "0.10.5"
@@ -2012,6 +2131,35 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "lettre"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a48c2e9831b370bc2d7233c2620298c45f3a158ed6b4b8d7416b2ada5a268fd8"
+dependencies = [
+ "async-trait",
+ "base64 0.21.2",
+ "chumsky",
+ "email-encoding",
+ "email_address",
+ "fastrand",
+ "futures-io",
+ "futures-util",
+ "hostname",
+ "httpdate",
+ "idna 0.5.0",
+ "mime",
+ "native-tls",
+ "nom",
+ "once_cell",
+ "quoted_printable",
+ "socket2 0.5.4",
+ "tokio",
+ "tokio-native-tls",
+ "url",
+ "uuid",
+]
+
 [[package]]
 name = "libc"
 version = "0.2.147"
@@ -2117,6 +2265,12 @@ dependencies = [
  "quote",
 ]
 
+[[package]]
+name = "match_cfg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
+
 [[package]]
 name = "md-5"
 version = "0.9.1"
@@ -2183,7 +2337,25 @@ dependencies = [
  "libc",
  "log",
  "wasi",
- "windows-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
 ]
 
 [[package]]
@@ -2261,7 +2433,7 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
 dependencies = [
- "hermit-abi 0.2.6",
+ "hermit-abi",
  "libc",
 ]
 
@@ -2277,6 +2449,50 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
+[[package]]
+name = "openssl"
+version = "0.10.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8"
+dependencies = [
+ "bitflags 2.4.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
 [[package]]
 name = "pad-adapter"
 version = "0.1.1"
@@ -2303,7 +2519,7 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets",
+ "windows-targets 0.48.0",
 ]
 
 [[package]]
@@ -2486,6 +2702,15 @@ dependencies = [
  "yansi",
 ]
 
+[[package]]
+name = "psm"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "ptr_meta"
 version = "0.1.4"
@@ -2542,6 +2767,12 @@ dependencies = [
  "syn 2.0.48",
 ]
 
+[[package]]
+name = "quoted_printable"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79ec282e887b434b68c18fe5c121d38e72a5cf35119b59e54ec5b992ea9c8eb0"
+
 [[package]]
 name = "radium"
 version = "0.7.0"
@@ -2644,6 +2875,7 @@ dependencies = [
  "leptos_actix",
  "leptos_meta",
  "leptos_router",
+ "lettre",
  "log",
  "pwhash",
  "regex",
@@ -2778,7 +3010,7 @@ dependencies = [
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -2836,6 +3068,15 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "schannel"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -2858,6 +3099,29 @@ version = "4.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
 
+[[package]]
+name = "security-framework"
+version = "2.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "self_cell"
 version = "1.0.1"
@@ -3132,7 +3396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
 dependencies = [
  "libc",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -3381,6 +3645,19 @@ dependencies = [
  "uuid",
 ]
 
+[[package]]
+name = "stacker"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "libc",
+ "psm",
+ "winapi",
+]
+
 [[package]]
 name = "stringprep"
 version = "0.1.3"
@@ -3453,16 +3730,7 @@ dependencies = [
  "fastrand",
  "redox_syscall",
  "rustix",
- "windows-sys",
-]
-
-[[package]]
-name = "termcolor"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
-dependencies = [
- "winapi-util",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -3541,7 +3809,17 @@ dependencies = [
  "pin-project-lite",
  "signal-hook-registry",
  "socket2 0.4.9",
- "windows-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
 ]
 
 [[package]]
@@ -3748,7 +4026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
 dependencies = [
  "form_urlencoded",
- "idna",
+ "idna 0.4.0",
  "percent-encoding",
 ]
 
@@ -3758,6 +4036,12 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1"
 
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
 [[package]]
 name = "uuid"
 version = "1.4.1"
@@ -3774,7 +4058,7 @@ version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd"
 dependencies = [
- "idna",
+ "idna 0.4.0",
  "lazy_static",
  "regex",
  "serde",
@@ -3979,7 +4263,7 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.0",
 ]
 
 [[package]]
@@ -3988,7 +4272,16 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -3997,13 +4290,28 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
 ]
 
 [[package]]
@@ -4012,42 +4320,84 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
 [[package]]
 name = "winnow"
 version = "0.5.19"
diff --git a/Cargo.toml b/Cargo.toml
index 1f2c500..dc7c9a8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,9 +30,10 @@ futures-util = "0.3.28"
 regex = "1.10.2"
 toml = "0.8.8"
 log = "0.4.20"
-env_logger = "0.10.1"
+env_logger = "0.11"
 getopts = "0.2.21"
 leptos-use = "0.10.1"
+lettre = {version = "0.11", features = ["tokio1-native-tls", "smtp-transport", "file-transport"], optional = true}
 
 [features]
 csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
@@ -43,6 +44,7 @@ ssr = [
   "dep:leptos_actix",
   "dep:actix-session",
   "dep:sqlx",
+  "dep:lettre",
   "leptos/ssr",
   "leptos_meta/ssr",
   "leptos_router/ssr",
diff --git a/src/backend/mail.rs b/src/backend/mail.rs
index 4cccd63..e332cd6 100644
--- a/src/backend/mail.rs
+++ b/src/backend/mail.rs
@@ -10,6 +10,13 @@ cfg_if! { if #[cfg(feature = "ssr")] {
     use crate::backend::get_pool;
     use crate::error::AppError;
     use log::info;
+    use crate::config::Mailing;
+    use crate::config::MailTransport;
+    use crate::backend::data::ResSumWithItems;
+    use lettre::message::Message as LettreMessage;
+    use lettre::{AsyncSmtpTransport, AsyncFileTransport, AsyncTransport, Tokio1Executor};
+    use lettre::transport::smtp::authentication::Credentials;
+    use std::ops::Add;
 
     pub async fn message_for_type(msg_type: &MessageType, pool: &PgPool) -> Result<Message, Error> {
         Ok(query_as::<_, Message>("SELECT * FROM message WHERE msg_type = $1")
@@ -48,6 +55,73 @@ cfg_if! { if #[cfg(feature = "ssr")] {
 
         Ok(())
     }
+
+    pub struct MailMessage {
+        reply_to: String,
+        to: String,
+        subject: String,
+        text: String
+    }
+
+    impl MailMessage {
+        pub fn new(reply_to: String, to: String, message: Message, reservation: &ResSumWithItems) -> Self {
+            Self {
+                reply_to,
+                to,
+                subject: message.subject,
+                text: Self::replace_body_vars(message.text, &reservation)
+            }
+        }
+
+        fn replace_body_vars(text: String, reservation: &ResSumWithItems) -> String {
+            text
+                .replace("#date#", &reservation.summary.date.format("%d. %m. %Y").to_string())
+                .replace("#summary#", &{
+                    let mut sum = "".to_string();
+                    for p in &reservation.reservations {
+                        sum = sum.add(&format!("{}: {} - {}",
+                            p.property.name,
+                            p.reservation.from.format("%H:%M").to_string(), p.reservation.to.format("%H:%M").to_string()));
+                    }
+                    sum
+                }
+            )
+        }
+
+        pub fn build_mail(&self, from: String) -> Result<LettreMessage, AppError> {
+            Ok(LettreMessage::builder()
+                .from(from.parse()?)
+                .reply_to(self.reply_to.parse()?)
+                .to(self.to.parse()?)
+                .subject(&self.subject)
+                .body(self.text.clone())?)
+        }
+    }
+
+    impl Mailing {
+        pub async fn send_mail(&self, msg: MailMessage) -> Result<(), AppError> {
+            match self.transport() {
+                MailTransport::Smtp => {
+                    let transport = if self.tls().unwrap_or(false) {
+                        AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&self.server().clone().unwrap_or_default())
+                    } else {
+                        AsyncSmtpTransport::<Tokio1Executor>::relay(&self.server().clone().unwrap_or_default())
+                    }.expect("Cannot create SMTP mail transport");
+                    if self.user().is_some() && self.password().is_some() {
+                        let cred = Credentials::new(self.user().clone().unwrap(), self.password().clone().unwrap());
+                        transport.credentials(cred).build().send(msg.build_mail(self.from().to_string())?).await?;
+                    } else {
+                        transport.build().send(msg.build_mail(self.from().to_string())?).await?;
+                    }
+                }
+                MailTransport::File => {
+                    AsyncFileTransport::<Tokio1Executor>::new(self.path().clone().unwrap_or_default())
+                        .send(msg.build_mail(self.from().to_string())?).await?;
+                }
+            }
+            Ok(())
+        }
+    }
 }}
 
 #[server]
diff --git a/src/backend/mod.rs b/src/backend/mod.rs
index 1b5d9c4..112eedf 100644
--- a/src/backend/mod.rs
+++ b/src/backend/mod.rs
@@ -54,27 +54,39 @@ cfg_if!{
         use actix_web::web::Data;
         use leptos_actix::extract;
         use leptos::ServerFnError;
+        use crate::config::Mailing;
 
         #[derive(Clone)]
         pub struct AppData {
-            db_pool: PgPool
+            db_pool: PgPool,
+            mailer: Mailing
         }
 
         impl AppData {
-            pub fn new(db_pool: PgPool) -> Self {
+            pub fn new(db_pool: PgPool, mailer: Mailing) -> Self {
                 Self {
-                    db_pool
+                    db_pool,
+                    mailer
                 }
             }
 
             pub fn db_pool(&self) -> &PgPool {
                 &self.db_pool
             }
+
+            pub fn mailer(&self) -> &Mailing {
+                &self.mailer
+            }
         }
 
         pub async fn get_pool() -> Result<PgPool, ServerFnError> {
             let data = extract::<Data<AppData>>().await?;
             Ok(data.db_pool().clone())
         }
+
+        pub async fn get_mailing() -> Result<Mailing, ServerFnError> {
+            let data = extract::<Data<AppData>>().await?;
+            Ok(data.mailer().clone())
+        }
     }
 }
\ No newline at end of file
diff --git a/src/backend/reservation.rs b/src/backend/reservation.rs
index 1e624f2..b103e08 100644
--- a/src/backend/reservation.rs
+++ b/src/backend/reservation.rs
@@ -13,8 +13,16 @@ cfg_if! { if #[cfg(feature = "ssr")] {
     use std::ops::DerefMut;
     use std::str::FromStr;
     use futures_util::future::join_all;
-    use crate::backend::data::{ReservationSum, ReservationState, ResWithProperty, Customer};
+    use log::warn;
+    use crate::backend::data::{ReservationSum, ReservationState, ResWithProperty, Customer, Message, MessageType};
     use crate::backend::get_pool;
+    use crate::backend::get_mailing;
+    use crate::backend::mail::MailMessage;
+    use crate::backend::mail::get_message;
+    use crate::error::AppError;
+    use sqlx::PgPool;
+    use crate::backend::user::admin_email;
+    use crate::backend::user::emails_for_notify;
 
     async fn find_sum_by_uuid(uuid: &Uuid, tx: &mut Transaction<'_, Postgres>) -> Result<ReservationSum, Error> {
         let reservation = query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE uuid = $1")
@@ -43,6 +51,39 @@ cfg_if! { if #[cfg(feature = "ssr")] {
         }
     }
 
+    async fn reservation_by_uuid(uuid: Uuid) -> Result<ResSumWithItems, ServerFnError> {
+        let pool = get_pool().await?;
+        let summary = query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE uuid = $1")
+            .bind(uuid)
+            .fetch_one(&pool)
+            .await?;
+        let sum_id = summary.id();
+        let cust_id = summary.customer;
+
+        Ok(ResSumWithItems{
+            summary,
+            customer: customer_for_reservation(cust_id, &pool).await?,
+            reservations: items_for_reservation(sum_id, &pool).await?
+        })
+    }
+
+    async fn items_for_reservation(id: i32, pool: &PgPool) -> Result<Vec<ResWithProperty>, ServerFnError> {
+        Ok(query_as::<_, ResWithProperty>(
+                "SELECT r.id, r.from, r.to, r.property, r.summary, p.name, p,description \
+                    FROM reservation as r  \
+                    JOIN property as p ON r.property = p.id WHERE r.summary = $1")
+            .bind(id)
+            .fetch_all(pool)
+            .await?)
+    }
+
+    async fn customer_for_reservation(id: i32, pool: &PgPool) -> Result<Customer, ServerFnError> {
+        Ok(query_as::<_, Customer>("SELECT * FROM customer WHERE id = $1")
+            .bind(id)
+            .fetch_one(pool)
+            .await?)
+    }
+
     async fn reservations_in_range(from: &NaiveDate, to: &NaiveDate, state: Option<ReservationState>) -> Result<Vec<ResSumWithItems>, ServerFnError> {
         let pool = get_pool().await?;
         let sums = if let Some(s) = state {
@@ -74,18 +115,9 @@ cfg_if! { if #[cfg(feature = "ssr")] {
             return Ok(vec![])
         }
 
-        let res:  Result<Vec<ResSumWithItems>, Error> = join_all(sums.into_iter().map(|s| async {
-            let reservations = query_as::<_, ResWithProperty>(
-                    "SELECT r.id, r.from, r.to, r.property, r.summary, p.name, p,description \
-                        FROM reservation as r  \
-                        JOIN property as p ON r.property = p.id WHERE r.summary = $1")
-                .bind(s.id())
-                .fetch_all(&pool)
-                .await?;
-            let customer = query_as::<_, Customer>("SELECT * FROM customer WHERE id = $1")
-                .bind(s.customer)
-                .fetch_one(&pool)
-                .await?;
+        let res:  Result<Vec<ResSumWithItems>, ServerFnError> = join_all(sums.into_iter().map(|s| async {
+            let reservations = items_for_reservation(s.id(), &pool).await?;
+            let customer = customer_for_reservation(s.customer, &pool).await?;
             Ok(ResSumWithItems {
                 summary: s,
                 customer,
@@ -106,6 +138,51 @@ cfg_if! { if #[cfg(feature = "ssr")] {
 
         Ok(())
     }
+
+    async fn notify_new_all(admin_mail: String, reservation: &ResSumWithItems) -> Result<(), AppError> {
+        let mailing = get_mailing().await?;
+        let msg = get_message(MessageType::NewReservation).await?;
+
+        for m in emails_for_notify().await? {
+            mailing.send_mail(MailMessage::new(admin_mail.clone(), m, msg.clone(), reservation)).await?;
+        }
+
+        Ok(())
+    }
+
+    async fn notify_new(uuid: Uuid) -> Result<(), AppError> {
+        let mailing = get_mailing().await?;
+        let msg = get_message(MessageType::NewReservationCust).await?;
+        let reservation = reservation_by_uuid(uuid).await?;
+        let admin_mail = admin_email().await;
+
+        if admin_mail.is_none() {
+            return Err(AppError::MailSendError("No admin mail".to_string()))
+        }
+
+        mailing.send_mail(MailMessage::new(admin_mail.clone().unwrap(), reservation.customer.email.clone(), msg, &reservation)).await?;
+        notify_new_all(admin_mail.unwrap(), &reservation).await
+    }
+
+    async fn send_notify(uuid: Uuid, msg: Message) -> Result<(), AppError> {
+        let mailing = get_mailing().await?;
+        let reservation = reservation_by_uuid(uuid).await?;
+        let admin_mail = admin_email().await;
+
+        if admin_mail.is_none() {
+            return Err(AppError::MailSendError("No admin mail".to_string()))
+        }
+
+        mailing.send_mail(MailMessage::new(admin_mail.unwrap(), reservation.customer.email.clone(), msg, &reservation)).await
+    }
+
+    async fn notify_approve(uuid: Uuid) -> Result<(), AppError> {
+        send_notify(uuid, get_message(MessageType::ReservationApp).await?).await
+    }
+
+    async fn notify_cancel(uuid: Uuid) -> Result<(), AppError> {
+        send_notify(uuid, get_message(MessageType::ReservationCanceled).await?).await
+    }
 }}
 
 #[server]
@@ -206,6 +283,10 @@ pub async fn create_reservation(reservation: CrReservation) -> Result<ApiRespons
 
     tx.commit().await?;
 
+    if let Err(e) = notify_new(res_uuid).await {
+        warn!("Notification not send: {}", e);
+    }
+
     Ok(ApiResponse::Data(reservation.date()))
 }
 
@@ -247,7 +328,12 @@ pub async fn approve(uuid: String) -> Result<ApiResponse<()>, ServerFnError> {
     use crate::backend::data::ReservationState;
 
     perm_check!(is_logged_in);
-    set_state(Uuid::parse_str(&uuid)?, ReservationState::Approved).await?;
+    let uuid = Uuid::parse_str(&uuid)?;
+    set_state(uuid, ReservationState::Approved).await?;
+
+    if let Err(e) = notify_approve(uuid).await {
+        warn!("Approve notification not send: {}", e);
+    }
 
     Ok(ApiResponse::Data(()))
 }
@@ -258,7 +344,12 @@ pub async fn cancel(uuid: String)  -> Result<ApiResponse<()>, ServerFnError> {
     use crate::backend::data::ReservationState;
 
     perm_check!(is_logged_in);
-    set_state(Uuid::parse_str(&uuid)?, ReservationState::Canceled).await?;
+    let uuid = Uuid::parse_str(&uuid)?;
+    set_state(uuid, ReservationState::Canceled).await?;
+
+    if let Err(e) = notify_cancel(uuid).await {
+        warn!("Cancel notification not send: {}", e);
+    }
 
     Ok(ApiResponse::Data(()))
 }
\ No newline at end of file
diff --git a/src/backend/user.rs b/src/backend/user.rs
index ebfcf57..76271ee 100644
--- a/src/backend/user.rs
+++ b/src/backend/user.rs
@@ -10,6 +10,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
     use leptos_actix::{extract, redirect};
     use log::{info, warn};
     use crate::error::AppError;
+    use crate::backend::get_pool;
 
     pub async fn has_admin_user(pool: &PgPool) -> Result<bool, Error> {
         let count: (i64,) = query_as(r#"SELECT COUNT(id) FROM "user" WHERE admin = $1"#)
@@ -68,6 +69,36 @@ cfg_if! { if #[cfg(feature = "ssr")] {
             false
         }
     }
+
+    pub async fn admin_email() -> Option<String> {
+        let pool = get_pool().await.ok()?;
+        let mail: Result<(String,), Error> = query_as(r#"SELECT email FROM "user" WHERE login = 'admin'"#)
+            .fetch_one(&pool)
+            .await;
+
+        if let Ok(m) = mail {
+            Some(m.0)
+        } else {
+            None
+        }
+    }
+
+    pub async fn emails_for_notify() -> Result<Vec<String>, ServerFnError> {
+        let pool = get_pool().await?;
+        let mails: Result<Vec<(String,)>, Error> = query_as(r#"SELECT email FROM "user" WHERE (email IS NOT NULL OR email <> '') AND get_emails = true"#)
+            .fetch_all(&pool)
+            .await;
+
+        if let Err(e) = mails {
+            if matches!(e, Error::RowNotFound) {
+                Ok(vec![])
+            } else {
+                Err(e.into())
+            }
+        } else {
+            Ok(mails.unwrap().into_iter().map(|m| m.0).collect())
+        }
+    }
 }}
 
 #[server]
diff --git a/src/config.rs b/src/config.rs
index f55b782..d4abf96 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -51,12 +51,58 @@ impl Database {
     }
 }
 
+#[cfg(feature = "ssr")]
+#[derive(Deserialize, Clone)]
+pub enum MailTransport {
+    Smtp,
+    File
+}
+
+#[cfg(feature = "ssr")]
+#[derive(Deserialize, Clone)]
+pub struct Mailing {
+    transport: MailTransport,
+    from: String,
+    path: Option<String>,
+    server: Option<String>,
+    user: Option<String>,
+    password: Option<String>,
+    tls: Option<bool>
+}
+
+#[cfg(feature = "ssr")]
+impl Mailing {
+    pub fn transport(&self) -> &MailTransport {
+        &self.transport
+    }
+    pub fn from(&self) -> &str {
+        &self.from
+    }
+    pub fn path(&self) -> &Option<String> {
+        &self.path
+    }
+    pub fn server(&self) -> &Option<String> {
+        &self.server
+    }
+    pub fn user(&self) -> &Option<String> {
+        &self.user
+    }
+    pub fn password(&self) -> &Option<String> {
+        &self.password
+    }
+    pub fn tls(&self) -> Option<bool> {
+        self.tls
+    }
+}
+
+
 #[cfg(feature = "ssr")]
 #[derive(Deserialize)]
 pub struct Configuration {
     session: Session,
     network: Network,
-    database: Database
+    database: Database,
+    mailing: Mailing
 }
 
 #[cfg(feature = "ssr")]
@@ -70,6 +116,9 @@ impl Configuration {
     pub fn database(&self) -> &Database {
         &self.database
     }
+    pub fn mailing(&self) -> &Mailing {
+        &self.mailing
+    }
 }
 
 #[cfg(feature = "ssr")]
diff --git a/src/error.rs b/src/error.rs
index affcc3c..07efc6b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -8,7 +8,9 @@ pub enum AppError {
     HourParseError,
     ServerError(String),
     FatalError(String),
-    SlotParseError
+    SlotParseError,
+    MailAddrParseErr(String),
+    MailSendError(String)
 }
 
 impl AppError {
@@ -17,7 +19,9 @@ impl AppError {
             AppError::HourParseError => {"Hour parse error".to_string()},
             AppError::ServerError(e) => {format!("Server error: {}", e)},
             AppError::FatalError(e) => {format!("Fatal error: {}", e)},
-            AppError::SlotParseError => {"Book slot parse error".to_string()}
+            AppError::SlotParseError => {"Book slot parse error".to_string()},
+            AppError::MailAddrParseErr(e) => {format!("Cannot parse email address: {}", e)},
+            AppError::MailSendError(e) => {format!("Cannot send email: {}", e)}
         }
     }
 }
@@ -48,3 +52,31 @@ impl From<ParseError> for AppError {
         AppError::HourParseError
     }
 }
+
+#[cfg(feature = "ssr")]
+impl From<lettre::address::AddressError> for AppError {
+    fn from(value: lettre::address::AddressError) -> Self {
+        AppError::MailAddrParseErr(value.to_string())
+    }
+}
+
+#[cfg(feature = "ssr")]
+impl From<lettre::error::Error> for AppError {
+    fn from(value: lettre::error::Error) -> Self {
+        AppError::MailSendError(value.to_string())
+    }
+}
+
+#[cfg(feature = "ssr")]
+impl From<lettre::transport::smtp::Error> for AppError {
+    fn from(value: lettre::transport::smtp::Error) -> Self {
+        AppError::MailSendError(value.to_string())
+    }
+}
+
+#[cfg(feature = "ssr")]
+impl From<lettre::transport::file::Error> for AppError {
+    fn from(value: lettre::transport::file::Error) -> Self {
+        AppError::MailSendError(value.to_string())
+    }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 5a8be24..b2e5b16 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -51,9 +51,10 @@ async fn main() -> std::io::Result<()> {
     let pool = PgPoolOptions::new()
         .max_connections(10)
         .connect(&srv_conf.database().con_string()).await.unwrap();
-
     migrate!().run(&pool).await.expect("could not run SQLx migrations");
 
+    let mailing = srv_conf.mailing().clone();
+
     if let Err(e) = create_admin(&pool).await {
         error!("Error while checking admin user: {:?}", e);
     }
@@ -69,7 +70,7 @@ async fn main() -> std::io::Result<()> {
         let site_root = &leptos_options.site_root;
 
         App::new()
-            .app_data(Data::new(AppData::new(pool.clone())))
+            .app_data(Data::new(AppData::new(pool.clone(), mailing.clone())))
             .wrap(Authentication)
             .wrap(SessionMiddleware::new(
                 CookieSessionStore::default(),