diff --git a/Cargo.lock b/Cargo.lock
index 1fafbc21d336d17a59c4bb0263f8325f96da4958..0c82027c73c6d7e285249c8110dc51b5783af01b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -30,6 +30,12 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "arrayvec"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
+
 [[package]]
 name = "async-attributes"
 version = "1.1.1"
@@ -258,6 +264,12 @@ dependencies = [
  "serde_json",
 ]
 
+[[package]]
+name = "bufstream"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
+
 [[package]]
 name = "bumpalo"
 version = "3.4.0"
@@ -332,6 +344,15 @@ dependencies = [
  "bitflags",
 ]
 
+[[package]]
+name = "cloudabi"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "concurrent-queue"
 version = "1.2.0"
@@ -647,8 +668,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bc1529d07bd55fa54fc6c2d8460f621cfde0ae3fd2b96b2fa7eb4883478e1703"
 dependencies = [
  "futures-core",
- "lock_api",
- "parking_lot",
+ "lock_api 0.3.4",
+ "parking_lot 0.10.2",
 ]
 
 [[package]]
@@ -927,6 +948,24 @@ dependencies = [
  "tokio-tls",
 ]
 
+[[package]]
+name = "hyperx"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9eae1ec4abdc4530fb001ebf585fd14e52ed17f0aacd3e13de497b71ed451750"
+dependencies = [
+ "base64 0.12.3",
+ "bytes 0.5.6",
+ "http",
+ "httparse",
+ "language-tags",
+ "log 0.4.11",
+ "mime 0.3.16",
+ "percent-encoding 2.1.0",
+ "time 0.1.43",
+ "unicase 2.6.0",
+]
+
 [[package]]
 name = "idna"
 version = "0.1.5"
@@ -979,6 +1018,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "instant"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485"
+
 [[package]]
 name = "iovec"
 version = "0.1.4"
@@ -1058,12 +1103,61 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
 
+[[package]]
+name = "lettre"
+version = "0.10.0-alpha.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deaf9b74d40fcb52d0f762eb08e45d5152b4db59d29bb73edd4cac7fe796862c"
+dependencies = [
+ "base64 0.12.3",
+ "bufstream",
+ "hostname",
+ "hyperx",
+ "idna 0.2.0",
+ "line-wrap",
+ "mime 0.3.16",
+ "nom",
+ "once_cell",
+ "quoted_printable",
+ "r2d2",
+ "regex",
+ "rustls",
+ "serde",
+ "serde_json",
+ "textnonce",
+ "uuid",
+ "webpki",
+ "webpki-roots 0.19.0",
+]
+
+[[package]]
+name = "lexical-core"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
+dependencies = [
+ "arrayvec",
+ "bitflags",
+ "cfg-if",
+ "ryu",
+ "static_assertions",
+]
+
 [[package]]
 name = "libc"
 version = "0.2.74"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
 
+[[package]]
+name = "line-wrap"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
+dependencies = [
+ "safemem",
+]
+
 [[package]]
 name = "linked-hash-map"
 version = "0.5.3"
@@ -1079,6 +1173,15 @@ dependencies = [
  "scopeguard",
 ]
 
+[[package]]
+name = "lock_api"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
+dependencies = [
+ "scopeguard",
+]
+
 [[package]]
 name = "log"
 version = "0.3.9"
@@ -1263,7 +1366,7 @@ dependencies = [
  "uuid",
  "version_check 0.9.2",
  "webpki",
- "webpki-roots",
+ "webpki-roots 0.18.0",
 ]
 
 [[package]]
@@ -1295,6 +1398,17 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "nom"
+version = "5.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
+dependencies = [
+ "lexical-core",
+ "memchr",
+ "version_check 0.9.2",
+]
+
 [[package]]
 name = "notify"
 version = "4.0.15"
@@ -1443,8 +1557,19 @@ version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
 dependencies = [
- "lock_api",
- "parking_lot_core",
+ "lock_api 0.3.4",
+ "parking_lot_core 0.7.2",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
+dependencies = [
+ "instant",
+ "lock_api 0.4.1",
+ "parking_lot_core 0.8.0",
 ]
 
 [[package]]
@@ -1454,7 +1579,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
 dependencies = [
  "cfg-if",
- "cloudabi",
+ "cloudabi 0.0.3",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
+dependencies = [
+ "cfg-if",
+ "cloudabi 0.1.0",
+ "instant",
  "libc",
  "redox_syscall",
  "smallvec",
@@ -1636,6 +1776,23 @@ dependencies = [
  "proc-macro2 1.0.19",
 ]
 
+[[package]]
+name = "quoted_printable"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2"
+
+[[package]]
+name = "r2d2"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f"
+dependencies = [
+ "log 0.4.11",
+ "parking_lot 0.11.0",
+ "scheduled-thread-pool",
+]
+
 [[package]]
 name = "rand"
 version = "0.6.5"
@@ -1756,7 +1913,7 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
 dependencies = [
- "cloudabi",
+ "cloudabi 0.0.3",
  "fuchsia-cprng",
  "libc",
  "rand_core 0.4.2",
@@ -1883,6 +2040,7 @@ dependencies = [
  "env_logger",
  "hashbrown",
  "lazy_static",
+ "lettre",
  "log 0.4.11",
  "lru",
  "mongodb",
@@ -2068,6 +2226,15 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "scheduled-thread-pool"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc6f74fd1204073fa02d5d5d68bec8021be4c38690b61264b2fdb48083d0e7d7"
+dependencies = [
+ "parking_lot 0.11.0",
+]
+
 [[package]]
 name = "scoped-tls"
 version = "1.0.0"
@@ -2289,6 +2456,12 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7345c971d1ef21ffdbd103a75990a15eb03604fc8b8852ca8cb418ee1a099028"
 
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
 [[package]]
 name = "stdweb"
 version = "0.4.20"
@@ -2423,6 +2596,19 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "textnonce"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acc659075a12c12c07bbb384862c352506707f6597f5b495f65427d08519b617"
+dependencies = [
+ "base64 0.12.3",
+ "byteorder",
+ "chrono",
+ "rand 0.7.3",
+ "serde",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.20"
@@ -2954,6 +3140,15 @@ dependencies = [
  "webpki",
 ]
 
+[[package]]
+name = "webpki-roots"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739"
+dependencies = [
+ "webpki",
+]
+
 [[package]]
 name = "wepoll-sys-stjepang"
 version = "1.0.6"
diff --git a/Cargo.toml b/Cargo.toml
index 664e23bd9e76a2081253662c8f27fdaf3d3dbc7f..691b0dfedf9886d553e3b9b000373b06e3725fc2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,3 +30,4 @@ lru = "0.6.0"
 lazy_static = "1.4.0"
 log = "0.4.11"
 env_logger = "0.7.1"
+lettre = "0.10.0-alpha.1"
diff --git a/Dockerfile b/Dockerfile
index fe32d3d0db9cb8b8f3b0a60dcc1c1403a9c8b617..33770248109b2236b0b70095a753d4b79af84fdb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,7 +10,7 @@ RUN cargo build --release
 
 # Bundle Stage
 FROM scratch
-COPY --from=builder /home/rust/src/revolt/target/x86_64-unknown-linux-musl/release/revolt .
+COPY --from=builder /home/rust/src/revolt/target/x86_64-unknown-linux-musl/release/revolt ./
 EXPOSE 8000
 EXPOSE 9000
 CMD ["./revolt"]
diff --git a/README.md b/README.md
index 4b3fbf010487b37610c848aa57f8f7a3dc325e27..5b4f89d84e231f6489fe6b942deba7d79c6f3c21 100644
--- a/README.md
+++ b/README.md
@@ -7,3 +7,14 @@ Features:
 - Distributed notification system, allowing any node to be seamlessly connected.
 - Simple deployment, based mostly on pure Rust code and libraries.
 - Hooks up to a MongoDB deployment, provide URI and no extra work needed.
+
+## Docker Helper Scripts
+
+If you have Docker installed, you can use the helper scripts to deploy Revolt in your development environment.
+
+```bash
+./build.sh   # build Docker image
+./run.sh     # run Docker container
+./monitor.sh # view container logs
+./remove.sh  # kill and remove container
+```
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..55ee6faadfab6ced567b0c1bd803fced46a1e0e4
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+docker build -t revolt --progress plain .
\ No newline at end of file
diff --git a/monitor.sh b/monitor.sh
new file mode 100644
index 0000000000000000000000000000000000000000..a15e793035972d0ffacb204a1873b216286f8515
--- /dev/null
+++ b/monitor.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+docker logs -f revolt
diff --git a/remove.sh b/remove.sh
new file mode 100644
index 0000000000000000000000000000000000000000..58b9af48509eeddebebc13b585daf595e6e960f6
--- /dev/null
+++ b/remove.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+echo "Removing Revolt container."
+docker kill revolt
+docker rm revolt
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000000000000000000000000000000000000..7f85141d62713e35a5338f8042f82e83a9fb2087
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+export $(egrep -v '^#' .env | xargs)
+echo "Running Revolt in detached mode."
+docker run \
+    -d \
+    --name revolt \
+    -p 8000:8000 \
+    -p 9000:9000 \
+    -e "DB_URI=$DB_URI" \
+    -e "PUBLIC_URI=$PUBLIC_URI" \
+    -e "PORTAL_URL=$PORTAL_URI" \
+    revolt