diff --git a/src/blog.rs b/src/blog.rs
index e2ab4ec4fa1c51cbff30c76521593a3aabc8fd1d..78ca55ad04a79f06c9b2884b2e27c5d6caea7f28 100644
--- a/src/blog.rs
+++ b/src/blog.rs
@@ -1,17 +1,16 @@
-use std::path::Path;
+use std::path::PathBuf;
 
 use anyhow::{bail, Error};
 use fnv::FnvHashMap;
 use warp::hyper::StatusCode;
 
 use crate::{
-    config::Logo, favicon::Favicon, image::MyImage, load::load_from_path, misc::Misc,
+    config::Config, favicon::Favicon, image::MyImage, load::load_from_path, misc::Misc,
     path::UrlPath, post::Post, resp::Response, style::Style,
 };
 
 pub struct Blog {
-    name: String,
-    description: String,
+    config: Config,
 
     pages: Vec<Post>,
     posts: Vec<Post>,
@@ -19,39 +18,33 @@ pub struct Blog {
     misc: Vec<Misc>,
     custom_style: Option<Style>,
     favicon: Option<Favicon>,
-    logo: Option<Logo>,
 }
 
 impl Blog {
-    pub fn new(
-        name: String,
-        description: String,
-        base_path: &Path,
-        logo: Option<Logo>,
-    ) -> Result<Self, Error> {
-        let pages = load_from_path(base_path, &base_path.join("pages"), "pages")?;
-        let posts = load_from_path(base_path, &base_path.join("posts"), "posts")?;
-        let imgs = load_from_path(base_path, &base_path.join("assets/img"), "")?;
-        let misc = load_from_path(base_path, &base_path.join("assets/misc"), "")?;
+    pub fn new(config: Config) -> Result<Self, Error> {
+        let base_path = PathBuf::from(&config.path);
+
+        let pages = load_from_path(&base_path, &base_path.join("pages"), "pages")?;
+        let posts = load_from_path(&base_path, &base_path.join("posts"), "posts")?;
+        let imgs = load_from_path(&base_path, &base_path.join("assets/img"), "")?;
+        let misc = load_from_path(&base_path, &base_path.join("assets/misc"), "")?;
         let custom_style = Style::load_if_exists(base_path.join("assets/style.css"))?;
         let favicon = Favicon::load_if_exists(base_path.join("assets/favicon.png"))?;
 
         Ok(Self {
-            name,
-            description,
+            config,
             pages,
             posts,
             imgs,
             misc,
             custom_style,
             favicon,
-            logo,
         })
     }
 
-    fn home(&mut self) -> Result<String, Error> {
-        let name = &self.name;
-        let description = &self.description;
+    fn home(&self) -> anyhow::Result<String> {
+        let name = &self.config.name;
+        let description = &self.config.description;
 
         let mut content = format!(
             r#"<div class="centered"><h1>{name}</h1>{description}</div><h2>Recent Posts</h2><table>"#
@@ -66,10 +59,32 @@ impl Blog {
         Ok(self.dress_page(None, &content))
     }
 
+    fn rss(&self) -> anyhow::Result<String> {
+        let mut out = format!(
+            concat!(
+                r#"<?xml version="1.0" encoding="UTF-8" ?>"#,
+                r#"<rss version="2.0">"#,
+                "<channel>",
+                "<title>{}</title>",
+                "<link>{}</link>",
+                "<description>{}</description>"
+            ),
+            self.config.name, self.config.root, self.config.description
+        );
+
+        for p in &self.posts {
+            out.push_str(&p.rss(&self.config.root)?);
+        }
+
+        out.push_str("</channel></rss>");
+
+        Ok(out)
+    }
+
     fn dress_page(&self, title: Option<&str>, content: &str) -> String {
         let title = match title {
-            Some(title) => format!("{} | {}", title.trim(), self.name.trim()),
-            None => self.name.trim().to_string(),
+            Some(title) => format!("{} | {}", title.trim(), self.config.name.trim()),
+            None => self.config.name.trim().to_string(),
         };
 
         let favicons = match &self.favicon {
@@ -77,7 +92,7 @@ impl Blog {
             None => "",
         };
 
-        let logo = match &self.logo {
+        let logo = match &self.config.logo {
             Some(logo) => format!(
                 r#"<img src="{}" width="{}" height="{}" alt="{}" class="align-right" />"#,
                 logo.path, logo.width, logo.height, logo.alt
@@ -152,6 +167,8 @@ impl TryFrom<Blog> for RenderedBlog {
             insert_path(&mut pages, &m.url.clone(), m.clone().response())?;
         }
 
+        insert_path(&mut pages, "/posts.rss", Response::rss(b.rss()?))?;
+
         let not_found =
             Response::html(b.dress_page(Some("Page not found"), include_str!("assets/404.html")));
 
diff --git a/src/config.rs b/src/config.rs
index 03a9fb6e5a04d363e375d6986840353580d55d84..6b273f010e6ff1b817f83a1f3869767d26bed2fe 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -16,6 +16,7 @@ pub struct Config {
     pub name: String,
     pub description: String,
     pub path: String,
+    pub root: String,
     pub logo: Option<Logo>,
 }
 
diff --git a/src/main.rs b/src/main.rs
index e99c73a3f8dc56c3d06be489fa27a9c5b3d0b4b0..e7ac1fc7d927e7da99396baedd1b4865e2d45e4a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,5 @@
 use config::Config;
-use std::{path::PathBuf, sync::Arc};
+use std::sync::Arc;
 use tokio::sync::RwLock;
 
 use blog::{Blog, RenderedBlog};
@@ -53,15 +53,7 @@ async fn handle_request(
 async fn main() {
     let config = Config::load().unwrap();
 
-    let blog: RenderedBlog = Blog::new(
-        config.name,
-        config.description,
-        &PathBuf::from(config.path),
-        config.logo,
-    )
-    .unwrap()
-    .try_into()
-    .unwrap();
+    let blog: RenderedBlog = Blog::new(config).unwrap().try_into().unwrap();
     let blog = Arc::new(RwLock::new(blog));
 
     let blog_filter = warp::any().map(move || blog.clone());
diff --git a/src/path.rs b/src/path.rs
index 6b56e821b512c3b0b7dee3141950d38daa4a69f7..497d8fa7c8925c8485fd74def7c5ba1bc6680a28 100644
--- a/src/path.rs
+++ b/src/path.rs
@@ -15,6 +15,16 @@ impl UrlPath {
 
         Self { parts }
     }
+
+    pub fn new_from_raw(s: String) -> Self {
+        Self { parts: vec![s] }
+    }
+
+    pub fn join(mut self, append: &str) -> Self {
+        self.parts.extend(UrlPath::new(append).parts);
+
+        self
+    }
 }
 
 impl Display for UrlPath {
diff --git a/src/post.rs b/src/post.rs
index 780012116686736b9518a15bd6395fd20aef5418..9c8d9c259e1f2b77e3f62aff3d8e34534815fa92 100644
--- a/src/post.rs
+++ b/src/post.rs
@@ -1,13 +1,14 @@
 use std::{cmp::Ordering, fs, path::Path};
 
 use anyhow::{bail, Context};
-use chrono::NaiveDate;
+use chrono::{DateTime, NaiveDate, Utc};
 use orgize::{elements::Keyword, export::HtmlHandler, Element};
 
 use crate::{
     date::parse_org_date,
     load::Loadable,
     parser::{parse, CustomHtmlHandler, Parser},
+    path::UrlPath,
     util::slugify,
 };
 
@@ -40,6 +41,29 @@ impl Post {
         Ok(out)
     }
 
+    pub fn rss(&self, root_url: &str) -> anyhow::Result<String> {
+        let title = &self.title;
+        let link = UrlPath::new_from_raw(root_url.to_string()).join(&self.url);
+        let date = match self.date {
+            Some(d) => {
+                format!(
+                    "<pubDate>{}</pubDate>",
+                    DateTime::<Utc>::from_utc(
+                        d.and_hms_opt(0, 0, 0)
+                            .context("Could not convert date to datetime")?,
+                        Utc
+                    )
+                    .to_rfc2822()
+                )
+            }
+            None => "".to_string(),
+        };
+
+        Ok(format!(
+            "<item><title>{title}</title><link>{link}</link><guid>{link}</guid>{date}</item>"
+        ))
+    }
+
     pub fn link(&self) -> anyhow::Result<String> {
         let link = match self.date_f()? {
             Some(date_f) => format!(
diff --git a/src/resp.rs b/src/resp.rs
index b3f45a348e7a1bbe968ced229ad009d2edcb19e0..e6c3aea739d7ac7373e6b6883427d1022fcf4407 100644
--- a/src/resp.rs
+++ b/src/resp.rs
@@ -33,6 +33,14 @@ impl Response {
         }
     }
 
+    pub fn rss(rss: String) -> Self {
+        Self {
+            content_type: "text/xml; charset=utf-8",
+            expiry: Expiry::Instant,
+            data: rss.into_bytes(),
+        }
+    }
+
     pub fn css(css: String) -> Self {
         Self {
             content_type: "text/css; charset=utf-8",