diff --git a/.gitignore b/.gitignore
index 56e4652f9fd0ebcd2030a07c6f3bbcb50d3bf56a..9780d9b6aec7b3df855ee7ad2bf51fd82cc70075 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
 .idea
 secrets.env
-data/
\ No newline at end of file
+app
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..35d70790c2ec55d48990a6129b6e4f19cc3defdf
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,10 @@
+FROM golang:alpine
+
+WORKDIR /app
+
+ADD templates /app/templates
+ADD app /app/app
+
+EXPOSE 8080
+
+ENTRYPOINT ["/app/app"]
\ No newline at end of file
diff --git a/api/send.go b/api/send.go
new file mode 100644
index 0000000000000000000000000000000000000000..eb5e8fae70b49e6d792f0a8156d962da8ceb659c
--- /dev/null
+++ b/api/send.go
@@ -0,0 +1,59 @@
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"git.sch.bme.hu/mikewashere/voxfrontend/db"
+	"git.sch.bme.hu/mikewashere/voxfrontend/languages"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+func SendHandler(w http.ResponseWriter, r *http.Request) {
+	b, e := ioutil.ReadAll(r.Body)
+	if e != nil {
+		_, _ = w.Write([]byte(e.Error()))
+		return
+	}
+
+	var msg db.Message
+	e = json.Unmarshal(b, &msg)
+	if e != nil {
+		_, _ = w.Write([]byte(e.Error()))
+		return
+	}
+
+	msg.Lang = languages.Voice(strings.TrimSpace(string(msg.Lang)))
+
+	u := db.GetUser(r)
+	msg.SchAcc = u.SchAcc
+	u.Lang = msg.Lang
+
+	resp, e := http.Get(fmt.Sprintf("http://vox.internal/uplink/duma/%s?%s", msg.Lang.ESpeak(), url.QueryEscape(msg.Message)))
+	if e != nil {
+		_, _ = w.Write([]byte(e.Error()))
+		return
+	}
+	b, e = ioutil.ReadAll(resp.Body)
+	if e != nil {
+		_, _ = w.Write([]byte(e.Error()))
+		return
+	}
+
+	e = db.SaveMsg(&msg)
+	if e != nil {
+		_, _ = w.Write([]byte(e.Error()))
+		return
+	}
+
+	go func() {
+		e := db.UpdateUser(&u)
+		if e != nil {
+			fmt.Println(e)
+		}
+	}()
+
+	_, _ = w.Write(b)
+}
diff --git a/cookies/data.go b/db/data.go
similarity index 52%
rename from cookies/data.go
rename to db/data.go
index 424c094daaa077006b1e16a09300777ec9989502..f62739d211badb30a256a5ad6612e1dba4cd2d5b 100644
--- a/cookies/data.go
+++ b/db/data.go
@@ -1,4 +1,4 @@
-package cookies
+package db
 
 import (
 	"fmt"
@@ -6,6 +6,8 @@ import (
 	"github.com/go-pg/pg/v10"
 	"github.com/go-pg/pg/v10/orm"
 	"net/http"
+	"os"
+	"strings"
 	"time"
 )
 
@@ -25,17 +27,39 @@ type Cookie struct {
 }
 
 type Message struct {
-	Id      int64     `pg:"id,pk"`
-	Sent    time.Time `pg:"sent,default:now()"`
-	Message string    `pg:"message"`
-	SchAcc  string    `pg:"user_schacc"`
-	User    *User     `pg:"rel:has-one"`
+	Id      int64           `pg:"id,pk"`
+	Sent    time.Time       `pg:"sent,default:now()"`
+	Message string          `pg:"message"`
+	SchAcc  string          `pg:"user_schacc"`
+	Lang    languages.Voice `pg:"lang,default:'🇭🇺'"`
+	User    *User           `pg:"rel:has-one"`
 }
 
 var db = pg.Connect(&pg.Options{
-	Addr:     "localhost:5432",
-	User:     "postgres",
-	Password: "postgres",
+	Addr: func() string {
+		host := os.Getenv("POSTGRES")
+		if host == "" {
+			host = "localhost"
+		}
+		if !strings.Contains(host, ":") {
+			host += ":5432"
+		}
+		return host
+	}(),
+	User: func() string {
+		us := os.Getenv("POSTGRES_USER")
+		if us == "" {
+			us = "postgres"
+		}
+		return us
+	}(),
+	Password: func() string {
+		pw := os.Getenv("POSTGRES_PASS")
+		if pw == "" {
+			pw = "postgres"
+		}
+		return pw
+	}(),
 })
 
 func init() {
@@ -56,6 +80,17 @@ func init() {
 	}
 }
 
+func GetLastMessages() ([]Message, error) {
+	var msgs []Message
+
+	err := db.Model(&msgs).Relation("User").Order("sent DESC").Limit(10).Select()
+	if err != nil {
+		return nil, err
+	}
+
+	return msgs, nil
+}
+
 func GetUser(r *http.Request) User {
 	c, e := r.Cookie(cookiename)
 	if e != nil {
@@ -74,6 +109,36 @@ func GetUser(r *http.Request) User {
 	return *cook.User
 }
 
+func Logout(w http.ResponseWriter, r *http.Request) {
+	c, e := r.Cookie(cookiename)
+	if e != nil {
+		return
+	}
+
+	cook := &Cookie{
+		Id: c.Value,
+	}
+
+	_, e = db.Model(cook).WherePK().Delete()
+
+	http.Redirect(w, r, "/", http.StatusFound)
+}
+
+func SwitchTheme(w http.ResponseWriter, r *http.Request) {
+	u := GetUser(r)
+	u.Dark = !u.Dark
+	e := UpdateUser(&u)
+	if e != nil {
+		fmt.Println(e)
+	}
+	http.Redirect(w, r, "/", http.StatusFound)
+}
+
+func UpdateUser(u *User) error {
+	_, e := db.Model(u).WherePK().Update()
+	return e
+}
+
 func SaveUser(w http.ResponseWriter, name, schacc string) error {
 	u := &User{
 		SchAcc: schacc,
@@ -107,3 +172,8 @@ func SaveUser(w http.ResponseWriter, name, schacc string) error {
 
 	return nil
 }
+
+func SaveMsg(msg *Message) error {
+	_, e := db.Model(msg).Insert(msg)
+	return e
+}
diff --git a/languages/languages.go b/languages/languages.go
index 4be2030e31a9ca133cb2762313d9845f0a42f3d3..9a01f031e07fb8bb960d21cb67f1e2b0f208c2af 100644
--- a/languages/languages.go
+++ b/languages/languages.go
@@ -15,6 +15,7 @@ var voices = map[Voice]string{
 	"🇭🇺": "hu",
 	"🇬🇧": "en",
 	"🇩🇪": "de",
+	"🤫":  "whisper",
 }
 
 var Voices = make([]Voice, 0, len(voices))
diff --git a/main.go b/main.go
index 25f1fd205ae3b070380270aa532fcb350a2e1df7..8a9c0b6140c951e483cd61b86e85664cf36663e0 100644
--- a/main.go
+++ b/main.go
@@ -3,7 +3,8 @@ package main
 import (
 	"fmt"
 	"git.sch.bme.hu/mikewashere/authsch"
-	"git.sch.bme.hu/mikewashere/voxfrontend/cookies"
+	"git.sch.bme.hu/mikewashere/voxfrontend/api"
+	"git.sch.bme.hu/mikewashere/voxfrontend/db"
 	"git.sch.bme.hu/mikewashere/voxfrontend/mainpage"
 	"net/http"
 	"os"
@@ -45,7 +46,7 @@ func authSuccess(details *authsch.AccDetails, w http.ResponseWriter, r *http.Req
 		return
 	}
 
-	e := cookies.SaveUser(w, details.DisplayName, details.LinkedAccounts.SchAcc)
+	e := db.SaveUser(w, details.DisplayName, details.LinkedAccounts.SchAcc)
 	if e != nil {
 		fmt.Println(e)
 	}
@@ -61,6 +62,9 @@ func main() {
 
 	mux.Handle("/", mainpage.CreateHandler(auth))
 	mux.Handle("/login/", auth.GetLoginHandler(authSuccess, authError))
+	mux.HandleFunc("/send/", api.SendHandler)
+	mux.HandleFunc("/logout/", db.Logout)
+	mux.HandleFunc("/themeswitch/", db.SwitchTheme)
 
 	e := http.ListenAndServe(":8080", mux)
 	if e != nil {
diff --git a/mainpage/handler.go b/mainpage/handler.go
index 4ad2e2206a1fec7d03a6b8356aa5d044c6fd06e9..20451bd6df3dff4746ffda5bc8e4902741c828e6 100644
--- a/mainpage/handler.go
+++ b/mainpage/handler.go
@@ -3,7 +3,7 @@ package mainpage
 import (
 	"fmt"
 	"git.sch.bme.hu/mikewashere/authsch"
-	"git.sch.bme.hu/mikewashere/voxfrontend/cookies"
+	"git.sch.bme.hu/mikewashere/voxfrontend/db"
 	"git.sch.bme.hu/mikewashere/voxfrontend/languages"
 	"html/template"
 	"net/http"
@@ -12,15 +12,16 @@ import (
 var tmpl *template.Template
 
 type indexData struct {
-	User     cookies.User
+	User     db.User
 	LoginURL string
 	Langs    []languages.Voice
 	DefLang  languages.Voice
+	Msgs     []db.Message
 }
 
 func init() {
 	var e error
-	tmpl, e = template.ParseFiles("mainpage/index.template.html")
+	tmpl, e = template.ParseFiles("templates/index.template.html")
 	if e != nil {
 		panic(e)
 	}
@@ -29,12 +30,18 @@ func init() {
 func CreateHandler(client authsch.Client) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		data := indexData{
-			User:     cookies.GetUser(r),
+			User:     db.GetUser(r),
 			LoginURL: client.GetAuthURL(),
 			Langs:    languages.Voices,
 			DefLang:  languages.DefaultVoice,
 		}
 
+		var err error
+		data.Msgs, err = db.GetLastMessages()
+		if err != nil {
+			fmt.Println(err)
+		}
+
 		e := tmpl.Execute(w, data)
 		if e != nil {
 			fmt.Println(e)
diff --git a/mainpage/index.template.html b/mainpage/index.template.html
deleted file mode 100644
index 43da48874d09068f62558a30dc4b4d42fef75f93..0000000000000000000000000000000000000000
--- a/mainpage/index.template.html
+++ /dev/null
@@ -1,64 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>Vox</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
-</head>
-<body class="{{ if .User.Dark }} bg-dark {{ end }}">
-
-    <nav class="navbar navbar-expand-lg {{ if .User.Dark }} navbar-dark {{ else }} navbar-light {{ end }} bg-primary">
-        <a class="navbar-brand" href="#">Vox</a>
-        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
-            <span class="navbar-toggler-icon"></span>
-        </button>
-        <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
-            <div class="navbar-nav mr-auto"></div>
-            <div class="navbar-nav">
-                {{ if .User.SchAcc }}
-                    <li class="nav-item dropdown">
-                        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            {{ .User.SchAcc }}
-                        </a>
-                        <div class="dropdown-menu {{ if .User.Dark }} bg-dark {{ end }}" style="width: auto; min-width: auto" aria-labelledby="navbarDropdown">
-                            <a class="nav-link" href="/logout" style="padding: 1em; width: auto">Log out</a>
-                        </div>
-                    </li>
-                {{ else }}
-                    <a class="nav-link" href="{{ .LoginURL }}">Log in</a>
-                {{ end }}
-            </div>
-        </div>
-    </nav>
-
-    <div class="container" style="padding: 2em">
-        {{ if .User.SchAcc }}
-            <div class="input-group mb-3">
-                <div class="input-group-prepend">
-                    <button class="btn btn-info dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                        {{ .User.Lang }}
-                    </button>
-                    <div class="dropdown-menu {{ if .User.Dark }} bg-dark text-white {{ end }}">
-                        {{ range .Langs }}
-                            <a class="dropdown-item {{ if $.User.Dark }} text-white {{ end }}" href="#">{{ . }} ({{ .ESpeak }})</a>
-                        {{ end }}
-                    </div>
-                </div>
-                <input type="text" class="form-control {{ if .User.Dark }} bg-secondary text-white {{ end }}" aria-label="Vox message">
-                <div class="input-group-append">
-                    <button class="btn btn-primary" type="button" id="button-addon2">Küldés!</button>
-                </div>
-            </div>
-        {{ else }}
-            <h1>
-                Be kell jelentkezned, ahhoz, hogy használhasd Vox-ot.
-            </h1>
-        {{ end }}
-    </div>
-
-    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
-    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
-    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
-</body>
-</html>
\ No newline at end of file
diff --git a/templates/index.template.html b/templates/index.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..e3258ad54a73cfbd1b41ea8000495815a3146f2b
--- /dev/null
+++ b/templates/index.template.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Vox</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
+</head>
+<body class="{{ if .User.Dark }} bg-dark {{ end }}">
+
+    <div id="overlay">
+        <h2 class="text-white" style="margin-top: 50vh; width: 100%; text-align: center">
+            <div class="spinner-border text-primary" role="status">
+                <span class="sr-only">Loading...</span>
+            </div>
+            Vox éppen beszél...
+        </h2>
+    </div>
+    <style>
+        #overlay {
+            visibility: hidden;
+            opacity: 0;
+            transition: visibility 0s, opacity 0.5s linear;
+
+            backdrop-filter: blur(10px);
+            background-color: rgba(0, 0, 0, 0.5);
+            position: fixed;
+            left: 0;
+            z-index: 999;
+            top: 0;
+            width: 100%;
+            height: 100%;
+        }
+    </style>
+
+    <nav class="navbar navbar-expand-lg {{ if .User.Dark }} navbar-dark {{ else }} navbar-light {{ end }} bg-primary">
+        <a class="navbar-brand" href="#">Vox</a>
+        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
+            <span class="navbar-toggler-icon"></span>
+        </button>
+        <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
+            <div class="navbar-nav mr-auto">
+                <a class="nav-link" href="/themeswitch/">Témaváltás</a>
+            </div>
+            <div class="navbar-nav">
+                {{ if .User.SchAcc }}
+                    <li class="nav-item dropdown">
+                        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            {{ .User.SchAcc }}
+                        </a>
+                        <div class="dropdown-menu {{ if .User.Dark }} bg-dark {{ end }}" style="width: auto; min-width: auto" aria-labelledby="navbarDropdown">
+                            <a class="nav-link" href="/logout" style="padding: 1em; width: auto">Log out</a>
+                        </div>
+                    </li>
+                {{ else }}
+                    <a class="nav-link" href="{{ .LoginURL }}">Log in</a>
+                {{ end }}
+            </div>
+        </div>
+    </nav>
+
+    <div class="container" style="padding: 2em">
+        {{ if .User.SchAcc }}
+            <div class="input-group mb-3">
+                <div class="input-group-prepend">
+                    <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="langbutton">
+                        {{ .User.Lang }}
+                    </button>
+                    <script>
+                        const btn = document.getElementById("langbutton");
+                        function changeLanguage(lang) {
+                            btn.innerText = lang;
+                        }
+                    </script>
+                    <div class="dropdown-menu {{ if .User.Dark }} bg-dark text-white {{ end }}">
+                        {{ range .Langs }}
+                            <a class="dropdown-item {{ if $.User.Dark }} text-white {{ end }}" onclick="changeLanguage({{ . }})">{{ . }} ({{ .ESpeak }})</a>
+                        {{ end }}
+                    </div>
+                </div>
+                <input id="msg" type="text" class="form-control {{ if .User.Dark }} bg-secondary text-white {{ end }}" aria-label="Vox message">
+                <div class="input-group-append">
+                    <button class="btn btn-primary" type="button" id="send">Küldés!</button>
+                </div>
+                <script>
+                    const lang = document.getElementById("langbutton");
+                    const msg = document.getElementById("msg");
+                    const overlay = document.getElementById("overlay");
+
+                    function send() {
+                        const obj = {
+                            Message: msg.value,
+                            Lang: lang.innerText,
+                        };
+
+
+                        overlay.style.visibility = "visible";
+                        overlay.style.opacity = "1";
+
+
+                        const xhr = new XMLHttpRequest();
+                        xhr.open("POST", "/send/", true);
+                        xhr.setRequestHeader("Content-Type", "application/json");
+                        xhr.send(JSON.stringify(obj));
+                        xhr.onload = () => {
+                            window.location.reload();
+                        }
+                    }
+
+                    msg.addEventListener("keyup", (event) => {
+                        if (event.keyCode === 13) {
+                            event.preventDefault();
+                            send();
+                        }
+                    });
+                    document.getElementById('send').onclick = send;
+                </script>
+            </div>
+            {{ if .Msgs }}
+                <div class="container">
+                    <div class="card">
+                        <div class="card-body" {{ if .User.Dark }} style="background: #545b62" {{ end }}>
+                            <table class="table {{ if .User.Dark }} text-white {{ end }}">
+                                <thead>
+                                <tr>
+                                    <th scope="row">Idő</th>
+                                    <th scope="row">Üzenet</th>
+                                    <th scope="row">Küldő</th>
+                                    <th scope="row">Nyelv</th>
+                                </tr>
+                                </thead>
+                                {{ range .Msgs }}
+                                    <tr>
+                                        <td>
+                                            {{ .Sent.Format "Jan 2 15:04" }}
+                                        </td>
+                                        <td>
+                                            {{ .Message }}
+                                        </td>
+                                        <td>
+                                            {{ .SchAcc }}
+                                        </td>
+                                        <td>
+                                            {{ .Lang }}
+                                        </td>
+                                    </tr>
+                                {{ end }}
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            {{ end }}
+        {{ else }}
+            <h1>
+                Be kell jelentkezned, ahhoz, hogy használhasd Vox-ot.
+            </h1>
+        {{ end }}
+    </div>
+
+
+
+    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
+    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
+</body>
+</html>
\ No newline at end of file