diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..84688ff
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,74 @@
+[build]
+functions = "netlify/functions"
+
+# Redirects from old AMP and HTML URLs
+[[redirects]]
+from = "/amp/:slug/"
+to = "/:slug/"
+status = 301
+force = true
+
+[[redirects]]
+from = "/:year/:month/:slug.html"
+to = "/:slug/"
+status = 301
+force = true
+
+# Rewrites for ActivityPub API endpoints
+[[redirects]]
+from = "/.well-known/*"
+to = "/.netlify/functions/well-known/:splat"
+status = 200
+
+[[redirects]]
+from = "/authorize_interaction"
+to = "/.netlify/functions/authorize_interaction"
+status = 200
+
+[[redirects]]
+from = "/@blog"
+to = "/.netlify/functions/actor"
+status = 200
+
+[[redirects]]
+from = "/coder"
+to = "/.netlify/functions/old"
+status = 200
+
+[[redirects]]
+from = "/followers"
+to = "/.netlify/functions/followers"
+status = 200
+
+[[redirects]]
+from = "/following"
+to = "/.netlify/functions/following"
+status = 200
+
+[[redirects]]
+from = "/inbox"
+to = "/.netlify/functions/inbox"
+status = 200
+
+[[redirects]]
+from = "/sharedInbox"
+to = "/.netlify/functions/sharedInbox"
+status = 200
+
+[[redirects]]
+from = "/outbox"
+to = "/.netlify/functions/outbox"
+status = 200
+
+# Rewrite for trailing slashes to index.html
+[[redirects]]
+from = "/:path/"
+to = "/:path/index.html"
+status = 200
+
+# Custom content-type for ActivityPub JSON
+[[headers]]
+for = "/*.json"
+  [headers.values]
+  Content-Type = "application/activity+json"
+
diff --git a/netlify/functions/actor.js b/netlify/functions/actor.js
new file mode 100644
index 0000000..6cb1b07
--- /dev/null
+++ b/netlify/functions/actor.js
@@ -0,0 +1,71 @@
+exports.handler = async (event, context) => {
+  const headers = event.headers;
+
+  if (headers.accept && headers.accept.includes("text/html")) {
+    return {
+      statusCode: 302,
+      headers: {
+        Location: "https://coder.is-a.dev/"
+      },
+    };
+  }
+
+  return {
+    statusCode: 200,
+    headers: {
+      "Content-Type": "application/activity+json"
+    },
+    body: JSON.stringify({
+      "@context": ["https://www.w3.org/ns/activitystreams", { "@language": "en-US" }],
+      "type": "Person",
+      "id": "https://coder.is-a.dev/@blog",
+      "outbox": "https://coder.is-a.dev/api/activitypub/outbox",
+      "following": "https://coder.is-a.dev/api/activitypub/following",
+      "followers": "https://coder.is-a.dev/api/activitypub/followers",
+      "sharedInbox": "https://coder.is-a.dev/api/activitypub/sharedInbox",
+      "inbox": "https://coder.is-a.dev/api/activitypub/inbox",
+      "url": "https://coder.is-a.dev/@blog",
+      "published": "2024-05-02T15:25:40Z",
+      "preferredUsername": "blog",
+      "name": "Deva Midhun's blog",
+      "manuallyApprovesFollowers": false,
+      "discoverable": true,
+      "indexable": true,
+      "memorial": false,
+      "summary": "Software developer & self-hosting enthusiast. This is a bridge between my blog and the fediverse!",
+      "tag": [],
+      "icon": {
+        "type": "Image",
+        "mediaType": "image/png",
+        "url": "https://i.ibb.co/N6J5b8WS/download20250102015611.png"
+      },
+      "publicKey": {
+        "id": "https://coder.is-a.dev/@blog#main-key",
+        "owner": "https://coder.is-a.dev/@blog",
+        "publicKeyPem": process.env.ACTIVITYPUB_PUBLIC_KEY || "MISSING_PUBLIC_KEY"
+      },
+      "attachment": [
+        {
+          "type": "PropertyValue",
+          "value": "https://coder.is-a.dev",
+          "name": "Website"
+        },
+        {
+          "type": "PropertyValue",
+          "value": "https://github.com/turbomaster95",
+          "name": "GitHub"
+        },
+        {
+          "type": "PropertyValue",
+          "value": "https://usr.cloud/@coder",
+          "name": "Owner"
+        },
+        {
+          "type": "PropertyValue",
+          "value": "https://keyoxide.org/6389542B98CB868DAC73A373ED1190B780583CF6",
+          "name": "Keyoxide"
+        }
+      ]
+    })
+  };
+};
diff --git a/netlify/functions/authorize_interaction.js b/netlify/functions/authorize_interaction.js
new file mode 100644
index 0000000..058b7c5
--- /dev/null
+++ b/netlify/functions/authorize_interaction.js
@@ -0,0 +1,9 @@
+exports.handler = async (event, context) => {
+  return {
+    statusCode: 200,
+    headers: {
+      "Content-Type": "application/jrd+json"
+    },
+    body: "ok"
+  };
+};
diff --git a/netlify/functions/followers.ts b/netlify/functions/followers.ts
new file mode 100644
index 0000000..998c8c3
--- /dev/null
+++ b/netlify/functions/followers.ts
@@ -0,0 +1,43 @@
+import { Handler } from '@netlify/functions';
+import { firestore } from '../../../lib/firebase';
+import { generateActor } from '../../../lib/actor';
+
+const handler: Handler = async (event, context) => {
+  const [, , username] = event.path.split('/'); // /users/:username/followers
+
+  try {
+    const followersSnapshot = await firestore
+      .collection('followers')
+      .where('following', '==', username)
+      .get();
+
+    const followers = followersSnapshot.docs.map(doc => ({
+      type: 'Link',
+      href: doc.data().follower,
+    }));
+
+    const actor = await generateActor(username);
+
+    return {
+      statusCode: 200,
+      headers: {
+        'Content-Type': 'application/activity+json',
+      },
+      body: JSON.stringify({
+        '@context': 'https://www.w3.org/ns/activitystreams',
+        id: `${actor.id}/followers`,
+        type: 'OrderedCollection',
+        totalItems: followers.length,
+        orderedItems: followers,
+      }),
+    };
+  } catch (error) {
+    return {
+      statusCode: 500,
+      body: JSON.stringify({ error: error.message }),
+    };
+  }
+};
+
+export { handler };
+
diff --git a/netlify/functions/following.ts b/netlify/functions/following.ts
new file mode 100644
index 0000000..7cee8e9
--- /dev/null
+++ b/netlify/functions/following.ts
@@ -0,0 +1,24 @@
+import { Handler } from '@netlify/functions';
+
+const handler: Handler = async (event, context) => {
+  const output = {
+    "@context": "https://www.w3.org/ns/activitystreams",
+    "id": "https://coder.is-a.dev/api/activitypub/following",
+    "type": "OrderedCollection",
+    "totalItems": 2,
+    "orderedItems": [
+      "https://mastodon.social/users/coderrrrr",
+      "https://usr.cloud/users/coder"
+    ]
+  };
+
+  return {
+    statusCode: 200,
+    headers: {
+      "Content-Type": "application/activity+json",
+    },
+    body: JSON.stringify(output),
+  };
+};
+
+export { handler };
diff --git a/netlify/functions/inbox.ts b/netlify/functions/inbox.ts
new file mode 100644
index 0000000..f28c3f5
--- /dev/null
+++ b/netlify/functions/inbox.ts
@@ -0,0 +1,46 @@
+import { Handler } from '@netlify/functions';
+import { firestore } from '../../../lib/firebase';
+import { verifySignature } from '../../../lib/verify';
+import { handleFollow } from '../../../lib/handleFollow';
+
+const handler: Handler = async (event, context) => {
+  if (event.httpMethod !== 'POST') {
+    return {
+      statusCode: 405,
+      body: 'Method Not Allowed',
+    };
+  }
+
+  try {
+    const inboxData = JSON.parse(event.body || '{}');
+    const username = event.path.split('/')[2]; // /users/:username/inbox
+
+    const isVerified = await verifySignature(event.headers, event.body);
+    if (!isVerified) {
+      return {
+        statusCode: 401,
+        body: 'Unauthorized',
+      };
+    }
+
+    if (inboxData.type === 'Follow') {
+      await handleFollow(inboxData, username);
+      return {
+        statusCode: 202,
+        body: 'Follow request accepted',
+      };
+    }
+
+    return {
+      statusCode: 200,
+      body: 'Activity received',
+    };
+  } catch (error) {
+    return {
+      statusCode: 500,
+      body: JSON.stringify({ error: error.message }),
+    };
+  }
+};
+
+export { handler };
diff --git a/netlify/functions/outbox.ts b/netlify/functions/outbox.ts
new file mode 100644
index 0000000..9c9eb88
--- /dev/null
+++ b/netlify/functions/outbox.ts
@@ -0,0 +1,20 @@
+import { Handler } from '@netlify/functions';
+import { join } from 'path';
+import { cwd } from 'process';
+import { readFileSync } from 'fs';
+
+const handler: Handler = async (event, context) => {
+  const file = join(cwd(), 'public', 'outbox.ajson');
+  const stringified = readFileSync(file, 'utf8');
+
+  return {
+    statusCode: 200,
+    headers: {
+      "Content-Type": "application/activity+json",
+    },
+    body: stringified,
+  };
+};
+
+export { handler };
+
diff --git a/netlify/functions/sendNote.ts b/netlify/functions/sendNote.ts
new file mode 100644
index 0000000..0674f92
--- /dev/null
+++ b/netlify/functions/sendNote.ts
@@ -0,0 +1,100 @@
+import { Handler } from '@netlify/functions';
+import { AP } from 'activitypub-core-types';
+import admin from 'firebase-admin';
+import { OrderedCollection } from 'activitypub-core-types/lib/activitypub/index.js';
+import { sendSignedRequest } from '../../lib/activitypub/utils/sendSignedRequest.js';
+import { fetchActorInformation } from '../../lib/activitypub/utils/fetchActorInformation.js';
+
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
+
+if (!admin.apps.length) {
+  admin.initializeApp({
+    credential: admin.credential.cert({
+      projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
+      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
+      privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
+    }),
+  });
+}
+
+const db = admin.firestore();
+
+const handler: Handler = async (event, context) => {
+  if (event.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) {
+    return {
+      statusCode: 401,
+      body: "Unauthorized",
+    };
+  }
+
+  const configRef = db.collection('config').doc('config');
+  const config = await configRef.get();
+  const lastId = config.exists ? config.data()?.lastId || '' : '';
+
+  // Fetch notes from outbox
+  const outboxResponse = await fetch("https://coder.is-a.dev/api/activitypub/outbox");
+  const outbox = await outboxResponse.json();
+
+  const followersSnapshot = await db.collection('followers').get();
+  let lastSuccessfulSentId = "";
+
+  const inboxes = new Set(); // Track unique inboxes to avoid duplicate sending
+
+  for (const followerDoc of followersSnapshot.docs) {
+    const follower = followerDoc.data();
+    const actorUrl = typeof follower.actor === 'string' ? follower.actor : follower.actor.id;
+
+    console.log(`Fetching actor information for ${actorUrl}`);
+    const actorInformation = await fetchActorInformation(actorUrl);
+    if (!actorInformation || !actorInformation.inbox) {
+      console.log(`Skipping ${actorUrl}: No valid inbox`);
+      continue;
+    }
+
+    const inboxUrl = actorInformation.sharedInbox || actorInformation.inbox;
+    inboxes.add(inboxUrl.toString()); // Add to set to ensure unique delivery
+  }
+
+  for (const item of outbox.orderedItems) {
+    if (item.id === lastId) {
+      console.log(`${item.id} has already been posted - skipping`);
+      break;
+    }
+
+    if (item.object) {
+      item.object.published = new Date().toISOString();
+    }
+
+    for (const inboxUrl of inboxes) {
+      try {
+        console.log(`Sending to ${inboxUrl}`);
+
+        const response = await sendSignedRequest(new URL(inboxUrl), item, {
+          headers: {
+            "Accept": "application/activity+json",
+            "Content-Type": "application/activity+json",
+          },
+        });
+
+        console.log(`Send result: ${response.status} ${response.statusText}`);
+        const responseText = await response.text();
+        console.log("Response body:", responseText);
+
+        lastSuccessfulSentId = item.id;
+      } catch (error) {
+        console.error(`Error sending to ${inboxUrl}:`, error);
+      }
+    }
+    break; // Only send the latest post for now
+  }
+
+  await configRef.set({ lastId: lastSuccessfulSentId });
+
+  return {
+    statusCode: 200,
+    body: "ok",
+  };
+};
+
+export { handler };
+