diff --git a/migration.sql b/migration.sql
index 55d2dd0..0459241 100644
--- a/migration.sql
+++ b/migration.sql
@@ -45,3 +45,9 @@ CREATE TABLE IF NOT EXISTS "user_trip" (
 	FOREIGN KEY(trip_details_id) REFERENCES trip_details(id),
 	CONSTRAINT unq UNIQUE (user_id, trip_details_id) -- allow user to participate only once for each trip
 );
+
+CREATE TABLE IF NOT EXISTS "log" (
+	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+	"msg" text NOT NULL,
+	"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
diff --git a/src/model/log.rs b/src/model/log.rs
new file mode 100644
index 0000000..80bab9a
--- /dev/null
+++ b/src/model/log.rs
@@ -0,0 +1,48 @@
+use rss::{ChannelBuilder, Item};
+use serde::{Deserialize, Serialize};
+use sqlx::{FromRow, SqlitePool};
+
+#[derive(FromRow, Debug, Serialize, Deserialize)]
+pub struct Log {
+    pub msg: String,
+    pub created_at: String,
+}
+
+impl Log {
+    pub async fn create(db: &SqlitePool, msg: String) -> bool {
+        sqlx::query!("INSERT INTO log(msg) VALUES (?)", msg,)
+            .execute(db)
+            .await
+            .is_ok()
+    }
+
+    async fn last(db: &SqlitePool) -> Vec<Log> {
+        sqlx::query_as!(
+            Log,
+            "
+SELECT msg, created_at
+FROM log 
+ORDER BY id DESC
+LIMIT 1000
+    "
+        )
+        .fetch_all(db)
+        .await
+        .unwrap()
+    }
+
+    pub async fn generate_feed(db: &SqlitePool) -> String {
+        let mut channel = ChannelBuilder::default()
+            .title("Ruder App Admin Feed")
+            .description("An RSS feed with activities from app.rudernlinz.at")
+            .build();
+        let mut items: Vec<Item> = vec![];
+        for log in Self::last(db).await {
+            let mut item = Item::default();
+            item.set_title(format!("({}) {}", log.created_at, log.msg));
+            items.append(&mut vec![item]);
+        }
+        channel.set_items(items);
+        channel.to_string()
+    }
+}
diff --git a/src/model/mod.rs b/src/model/mod.rs
index c7742b8..03530f7 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -7,6 +7,7 @@ use self::{
     trip::{Trip, TripWithUser},
 };
 
+pub mod log;
 pub mod planned_event;
 pub mod trip;
 pub mod tripdetails;
diff --git a/src/rest/admin/mod.rs b/src/rest/admin/mod.rs
index 4733b2f..4aacf47 100644
--- a/src/rest/admin/mod.rs
+++ b/src/rest/admin/mod.rs
@@ -1,11 +1,13 @@
 use rocket::Route;
 
 pub mod planned_event;
+pub mod rss;
 pub mod user;
 
 pub fn routes() -> Vec<Route> {
     let mut ret = Vec::new();
     ret.append(&mut user::routes());
     ret.append(&mut planned_event::routes());
+    ret.append(&mut rss::routes());
     ret
 }
diff --git a/src/rest/admin/rss.rs b/src/rest/admin/rss.rs
new file mode 100644
index 0000000..71d7044
--- /dev/null
+++ b/src/rest/admin/rss.rs
@@ -0,0 +1,17 @@
+use crate::rest::Log;
+use rocket::{get, routes, Route, State};
+use sqlx::SqlitePool;
+
+#[get("/rss?<key>")]
+async fn index(db: &State<SqlitePool>, key: Option<&str>) -> String {
+    match key {
+        Some(key) if key.eq("G9h/f2MFEr408IaB4Yd67/maVSsnAJNjcaZ2Tzl5Vo=") => {
+            Log::generate_feed(db).await
+        }
+        _ => "Not allowed".to_string(),
+    }
+}
+
+pub fn routes() -> Vec<Route> {
+    routes![index]
+}
diff --git a/src/rest/auth.rs b/src/rest/auth.rs
index 41efbad..84da633 100644
--- a/src/rest/auth.rs
+++ b/src/rest/auth.rs
@@ -11,7 +11,10 @@ use rocket_dyn_templates::{context, tera, Template};
 use serde_json::json;
 use sqlx::SqlitePool;
 
-use crate::model::user::{LoginError, User};
+use crate::model::{
+    log::Log,
+    user::{LoginError, User},
+};
 
 #[get("/")]
 fn index(flash: Option<FlashMessage<'_>>) -> Template {
@@ -96,6 +99,8 @@ async fn updatepw(
     let user_json: String = format!("{}", json!(user));
     cookies.add_private(Cookie::new("loggedin_user", user_json));
 
+    Log::create(db, format!("User {} set her password.", user.name)).await;
+
     Flash::success(
         Redirect::to("/"),
         "Passwort erfolgreich gesetzt. Du bist nun eingeloggt.",
diff --git a/src/rest/cox.rs b/src/rest/cox.rs
index 8085480..24f584e 100644
--- a/src/rest/cox.rs
+++ b/src/rest/cox.rs
@@ -7,6 +7,7 @@ use rocket::{
 use sqlx::SqlitePool;
 
 use crate::model::{
+    log::Log,
     trip::{CoxHelpError, Trip, TripDeleteError, TripUpdateError},
     tripdetails::TripDetails,
     user::CoxUser,
@@ -36,6 +37,18 @@ async fn create(db: &State<SqlitePool>, data: Form<AddTripForm>, cox: CoxUser) -
     //TODO: fix clone()
     Trip::new_own(db, cox.id, trip_details_id).await;
 
+    Log::create(
+        db,
+        format!(
+            "Cox {} created trip on {} @ {} for {} rower",
+            cox.name,
+            data.day.clone(),
+            data.planned_starting_time.clone(),
+            data.max_people,
+        ),
+    )
+    .await;
+
     Flash::success(Redirect::to("/"), "Ausfahrt erfolgreich erstellt.")
 }
 
@@ -66,7 +79,17 @@ async fn update(
 #[get("/join/<planned_event_id>")]
 async fn join(db: &State<SqlitePool>, planned_event_id: i64, cox: CoxUser) -> Flash<Redirect> {
     match Trip::new_join(db, cox.id, planned_event_id).await {
-        Ok(_) => Flash::success(Redirect::to("/"), "Danke für's helfen!"),
+        Ok(_) => {
+            Log::create(
+                db,
+                format!(
+                    "Cox {} helps at planned_event.id={}",
+                    cox.name, planned_event_id,
+                ),
+            )
+            .await;
+            Flash::success(Redirect::to("/"), "Danke für's helfen!")
+        }
         Err(CoxHelpError::AlreadyRegisteredAsCox) => {
             Flash::error(Redirect::to("/"), "Du hilfst bereits aus!")
         }
@@ -80,7 +103,10 @@ async fn join(db: &State<SqlitePool>, planned_event_id: i64, cox: CoxUser) -> Fl
 #[get("/remove/trip/<trip_id>")]
 async fn remove_trip(db: &State<SqlitePool>, trip_id: i64, cox: CoxUser) -> Flash<Redirect> {
     match Trip::delete(db, cox.id, trip_id).await {
-        Ok(_) => Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!"),
+        Ok(_) => {
+            Log::create(db, format!("Cox {} deleted trip.id={}", cox.name, trip_id)).await;
+            Flash::success(Redirect::to("/"), "Erfolgreich gelöscht!")
+        }
         Err(TripDeleteError::SomebodyAlreadyRegistered) => Flash::error(
             Redirect::to("/"),
             "Ausfahrt kann nicht gelöscht werden, da bereits jemand registriert ist!",
@@ -95,6 +121,15 @@ async fn remove_trip(db: &State<SqlitePool>, trip_id: i64, cox: CoxUser) -> Flas
 async fn remove(db: &State<SqlitePool>, planned_event_id: i64, cox: CoxUser) -> Flash<Redirect> {
     Trip::delete_by_planned_event_id(db, cox.id, planned_event_id).await;
 
+    Log::create(
+        db,
+        format!(
+            "Cox {} deleted registration for planned_event.id={}",
+            cox.name, planned_event_id
+        ),
+    )
+    .await;
+
     Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!")
 }
 
diff --git a/src/rest/mod.rs b/src/rest/mod.rs
index 16ca6a8..004fbbe 100644
--- a/src/rest/mod.rs
+++ b/src/rest/mod.rs
@@ -11,6 +11,7 @@ use rocket_dyn_templates::{tera::Context, Template};
 use sqlx::SqlitePool;
 
 use crate::model::{
+    log::Log,
     user::User,
     usertrip::{UserTrip, UserTripError},
     Day,
@@ -51,7 +52,17 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_
 #[get("/join/<trip_details_id>")]
 async fn join(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash<Redirect> {
     match UserTrip::create(db, user.id, trip_details_id).await {
-        Ok(_) => Flash::success(Redirect::to("/"), "Erfolgreich angemeldet!"),
+        Ok(_) => {
+            Log::create(
+                db,
+                format!(
+                    "User {} registered for trip_details.id={}",
+                    user.name, trip_details_id
+                ),
+            )
+            .await;
+            Flash::success(Redirect::to("/"), "Erfolgreich angemeldet!")
+        }
         Err(UserTripError::EventAlreadyFull) => {
             Flash::error(Redirect::to("/"), "Event bereits ausgebucht!")
         }
@@ -68,6 +79,15 @@ async fn join(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash
 async fn remove(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash<Redirect> {
     UserTrip::delete(db, user.id, trip_details_id).await;
 
+    Log::create(
+        db,
+        format!(
+            "User {} unregistered for trip_details.id={}",
+            user.name, trip_details_id
+        ),
+    )
+    .await;
+
     Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!")
 }