diff --git a/src/main.rs b/src/main.rs
index c1f7aa3..57e951b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -31,11 +31,13 @@ async fn setup_inmem_store() -> outbound::repository::adapters::memory::InMemory
 
     let store = outbound::repository::adapters::memory::InMemoryStore::new();
     store
-        .add_site(outbound::repository::site::SiteMetadata {
+        .create_site(outbound::repository::site::SiteMetadata {
             domain: "localhost".to_string(),
             title: "Test site".to_string(),
         })
-        .await;
+        .await
+        .unwrap();
+
     store
         .set_page(
             "localhost",
@@ -56,6 +58,7 @@ async fn setup_inmem_store() -> outbound::repository::adapters::memory::InMemory
         )
         .await
         .unwrap();
+
     store
         .set_page(
             "localhost",
@@ -73,6 +76,7 @@ async fn setup_inmem_store() -> outbound::repository::adapters::memory::InMemory
         )
         .await
         .unwrap();
+
     store
         .add_post(
             "localhost",
@@ -85,6 +89,7 @@ async fn setup_inmem_store() -> outbound::repository::adapters::memory::InMemory
             },
         )
         .await;
+
     store
         .add_post(
             "localhost",
diff --git a/src/outbound/repository/adapters/memory.rs b/src/outbound/repository/adapters/memory.rs
index 95822bb..81974a0 100644
--- a/src/outbound/repository/adapters/memory.rs
+++ b/src/outbound/repository/adapters/memory.rs
@@ -36,14 +36,6 @@ impl InMemoryStore {
         }
     }
 
-    pub async fn add_site(&self, site: SiteMetadata) {
-        self.data
-            .lock()
-            .await
-            .sites
-            .insert(site.domain.clone(), site);
-    }
-
     pub async fn add_post(&self, site: &str, collection_id: &str, post_id: &str, post: Post) {
         self.data
             .lock()
@@ -61,11 +53,12 @@ impl InMemoryStore {
         let store = InMemoryStore::new();
 
         store
-            .add_site(SiteMetadata {
+            .create_site(SiteMetadata {
                 domain: "example.com".to_string(),
                 title: "Test site".to_string(),
             })
-            .await;
+            .await
+            .unwrap();
 
         store
             .set_page(
@@ -167,6 +160,21 @@ impl SiteRepository for InMemoryStore {
         Ok(pages)
     }
 
+    async fn create_site(&self, site: SiteMetadata) -> Result<()> {
+        // Check for existing site
+        if self.data.lock().await.sites.contains_key(&site.domain) {
+            return Err(repository::site::Error::Conflict);
+        }
+
+        self.data
+            .lock()
+            .await
+            .sites
+            .insert(site.domain.clone(), site);
+
+        Ok(())
+    }
+
     async fn get_page(&self, domain: &str, page: &str) -> Result<Page> {
         self.data
             .lock()
@@ -187,6 +195,21 @@ impl SiteRepository for InMemoryStore {
         Ok(())
     }
 
+    async fn delete_page(&self, domain: &str, page: &str) -> Result<()> {
+        let deleted = self
+            .data
+            .lock()
+            .await
+            .pages
+            .remove(&(domain.to_string(), page.to_string()));
+
+        if deleted.is_none() {
+            return Err(repository::site::Error::NotFound);
+        }
+
+        Ok(())
+    }
+
     async fn get_post(&self, domain: &str, collection_id: &str, post_id: &str) -> Result<Post> {
         self.data
             .lock()
@@ -259,7 +282,10 @@ mod tests {
             cursor::CursorOptions,
             site::{Block, Page, PageContent, PageInfo, Post},
         },
-        outbound::repository::{self, site::SiteRepository},
+        outbound::repository::{
+            self,
+            site::{SiteMetadata, SiteRepository},
+        },
     };
 
     use super::InMemoryStore;
@@ -445,4 +471,57 @@ mod tests {
         assert_eq!(page.info.name, "new-page");
         assert_eq!(page.content, content);
     }
+
+    #[tokio::test]
+    async fn delete_page_works() {
+        let store = InMemoryStore::with_test_data().await;
+        let _ = store.delete_page("example.com", "/").await.unwrap();
+        let result = store.get_page("example.com", "/").await;
+        assert!(result.is_err());
+        assert!(matches!(
+            result.err().unwrap(),
+            repository::site::Error::NotFound
+        ));
+    }
+
+    #[tokio::test]
+    async fn delete_page_for_nonexistent_fails() {
+        let store = InMemoryStore::new();
+        let result = store.delete_page("example.com", "/").await;
+        assert!(result.is_err());
+        assert!(matches!(
+            result.err().unwrap(),
+            repository::site::Error::NotFound
+        ));
+    }
+
+    #[tokio::test]
+    async fn creates_a_site() {
+        let store = InMemoryStore::new();
+        let _ = store
+            .create_site(SiteMetadata {
+                domain: "example.com".to_string(),
+                title: "Test site".to_string(),
+            })
+            .await;
+        let info = store.get_site_by_domain("example.com").await.unwrap();
+        assert_eq!(info.domain, "example.com");
+        assert_eq!(info.title, "Test site");
+    }
+
+    #[tokio::test]
+    async fn doesnt_overwrite_existing_site() {
+        let store = InMemoryStore::with_test_data().await;
+        let result = store
+            .create_site(SiteMetadata {
+                domain: "example.com".to_string(),
+                title: "Test site".to_string(),
+            })
+            .await;
+        assert!(result.is_err());
+        assert!(matches!(
+            result.err().unwrap(),
+            repository::site::Error::Conflict
+        ));
+    }
 }
diff --git a/src/outbound/repository/site.rs b/src/outbound/repository/site.rs
index b420ff4..735ff5d 100644
--- a/src/outbound/repository/site.rs
+++ b/src/outbound/repository/site.rs
@@ -17,8 +17,11 @@ pub trait SiteRepository: Send + Sync + 'static {
     async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata>;
     async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>>;
 
+    async fn create_site(&self, site: SiteMetadata) -> Result<()>;
+
     async fn get_page(&self, domain: &str, name: &str) -> Result<Page>;
     async fn set_page(&self, domain: &str, page: Page) -> Result<()>;
+    async fn delete_page(&self, domain: &str, page: &str) -> Result<()>;
 
     async fn get_post(&self, domain: &str, collection_id: &str, id: &str) -> Result<Post>;
     async fn get_posts_for_collection(
@@ -36,6 +39,9 @@ pub enum Error {
     #[error("resource not found")]
     NotFound,
 
+    #[error("a resource with the same identifier already exists")]
+    Conflict,
+
     #[error("the server encountered an error: {0}")]
     ServerError(String),
 }
diff --git a/src/outbound/services/site.rs b/src/outbound/services/site.rs
index 1a33178..3300755 100644
--- a/src/outbound/services/site.rs
+++ b/src/outbound/services/site.rs
@@ -7,7 +7,10 @@ use crate::{
         cursor::{CursorOptions, Paginated},
         site::{Page, Post, SiteInfo},
     },
-    outbound::repository::{self, site::SiteRepository},
+    outbound::repository::{
+        self,
+        site::{SiteMetadata, SiteRepository},
+    },
 };
 
 #[derive(Clone)]
@@ -36,18 +39,34 @@ impl SiteService {
         })
     }
 
+    pub async fn create_site(&self, site: SiteMetadata) -> Result<()> {
+        self.site_repository.create_site(site).await?;
+
+        Ok(())
+    }
+
     pub async fn get_page(&self, domain: &str, name: &str) -> Result<Page> {
-        let info = self.site_repository.get_site_by_domain(domain).await?;
-        let page = self.site_repository.get_page(&info.domain, name).await?;
+        let page = self.site_repository.get_page(domain, name).await?;
 
         Ok(page)
     }
 
+    pub async fn set_page(&self, domain: &str, page: Page) -> Result<()> {
+        self.site_repository.set_page(domain, page).await?;
+
+        Ok(())
+    }
+
+    pub async fn delete_page(&self, domain: &str, page: &str) -> Result<()> {
+        self.site_repository.delete_page(domain, page).await?;
+
+        Ok(())
+    }
+
     pub async fn get_post(&self, domain: &str, collection_id: &str, id: &str) -> Result<Post> {
-        let info = self.site_repository.get_site_by_domain(domain).await?;
         let post = self
             .site_repository
-            .get_post(&info.domain, collection_id, id)
+            .get_post(domain, collection_id, id)
             .await?;
 
         Ok(post)
@@ -59,11 +78,10 @@ impl SiteService {
         collection_id: &str,
         cursor: Option<String>,
     ) -> Result<Paginated<Post, String>> {
-        let info = self.site_repository.get_site_by_domain(domain).await?;
         let posts = self
             .site_repository
             .get_posts_for_collection(
-                &info.domain,
+                domain,
                 collection_id,
                 CursorOptions {
                     after: cursor,
@@ -74,13 +92,6 @@ impl SiteService {
 
         Ok(posts)
     }
-
-    pub async fn set_page(&self, domain: &str, page: Page) -> Result<()> {
-        let info = self.site_repository.get_site_by_domain(domain).await?;
-        self.site_repository.set_page(&info.domain, page).await?;
-
-        Ok(())
-    }
 }
 
 pub type Result<T> = std::result::Result<T, Error>;
@@ -108,7 +119,7 @@ mod tests {
     use super::{Error, SiteService};
     use crate::{
         domain::entities::site::{Block, Page, PageContent, PageInfo, Post},
-        outbound::repository::adapters::memory::InMemoryStore,
+        outbound::repository::{adapters::memory::InMemoryStore, site::SiteMetadata},
     };
 
     #[tokio::test]
@@ -244,4 +255,49 @@ mod tests {
         assert_eq!(page.info.name, "new_page");
         assert_eq!(page.content, content);
     }
+
+    #[tokio::test]
+    async fn deletes_a_page() {
+        let service = SiteService::new(InMemoryStore::with_test_data().await);
+        service.delete_page("example.com", "about").await.unwrap();
+        let result = service.get_page("example.com", "about").await;
+        assert!(result.is_err());
+        assert!(matches!(result.err().unwrap(), Error::NotFound));
+    }
+
+    #[tokio::test]
+    async fn deletes_a_nonexistent_page() {
+        let service = SiteService::new(InMemoryStore::with_test_data().await);
+        let result = service.delete_page("example.com", "nonexistent").await;
+        assert!(result.is_err());
+        assert!(matches!(result.err().unwrap(), Error::NotFound));
+    }
+
+    #[tokio::test]
+    async fn creates_a_site() {
+        let service = SiteService::new(InMemoryStore::with_test_data().await);
+        service
+            .create_site(SiteMetadata {
+                domain: "new.com".to_string(),
+                title: "New site".to_string(),
+            })
+            .await
+            .unwrap();
+    }
+
+    #[tokio::test]
+    async fn doesnt_create_a_site_that_already_exists() {
+        let service = SiteService::new(InMemoryStore::with_test_data().await);
+        let result = service
+            .create_site(SiteMetadata {
+                domain: "example.com".to_string(),
+                title: "Example site".to_string(),
+            })
+            .await;
+        assert!(result.is_err());
+        assert!(matches!(
+            result.err().unwrap(),
+            Error::RepositoryError(crate::outbound::repository::site::Error::Conflict)
+        ));
+    }
 }