diff --git a/modules/rsocials/mod.ts b/modules/rsocials/mod.ts
new file mode 100644
index 0000000..f5bb6bd
--- /dev/null
+++ b/modules/rsocials/mod.ts
@@ -0,0 +1,120 @@
+/**
+ * rSocials module — campaign strategy workflow builder.
+ *
+ * Proxies campaign API + embeds the Next.js campaign editor from rsocials:3000.
+ * Page routes render an iframe inside the rSpace shell so the campaign builder
+ * gets the full rSpace header/nav while the Next.js app runs independently.
+ */
+
+import { Hono } from "hono";
+import { renderShell } from "../../server/shell";
+import type { RSpaceModule } from "../../shared/module";
+import { getModuleInfoList } from "../../shared/module";
+
+const RSOCIALS_INTERNAL = process.env.RSOCIALS_URL || "http://rsocials:3000";
+const RSOCIALS_PUBLIC = "https://rsocials.online";
+
+const routes = new Hono();
+
+// ── API proxy ────────────────────────────────────────────
+// Proxy all campaign API calls to the rsocials container
+
+routes.get("/api/campaigns", async (c) => {
+ const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns`);
+ return c.json(await res.json(), res.status as StatusCode);
+});
+
+routes.get("/api/campaigns/:id", async (c) => {
+ const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns/${c.req.param("id")}`);
+ return c.json(await res.json(), res.status as StatusCode);
+});
+
+routes.post("/api/campaigns", async (c) => {
+ const body = await c.req.text();
+ const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body,
+ });
+ return c.json(await res.json(), res.status as StatusCode);
+});
+
+routes.put("/api/campaigns/:id", async (c) => {
+ const body = await c.req.text();
+ const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns/${c.req.param("id")}`, {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body,
+ });
+ return c.json(await res.json(), res.status as StatusCode);
+});
+
+routes.delete("/api/campaigns/:id", async (c) => {
+ const res = await fetch(`${RSOCIALS_INTERNAL}/api/campaigns/${c.req.param("id")}`, {
+ method: "DELETE",
+ });
+ return c.json(await res.json(), res.status as StatusCode);
+});
+
+// ── Page routes ──────────────────────────────────────────
+
+function campaignFrame(src: string, space: string, title: string) {
+ return renderShell({
+ title,
+ moduleId: "rsocials",
+ spaceSlug: space,
+ modules: getModuleInfoList(),
+ theme: "dark",
+ styles: ``,
+ body: `
+
+ `,
+ });
+}
+
+// /rsocials/ → campaign list
+routes.get("/", (c) => {
+ const space = c.req.param("space") || "demo";
+ return c.html(
+ campaignFrame(`${RSOCIALS_PUBLIC}/campaigns`, space, `Campaigns | rSocials`)
+ );
+});
+
+// /rsocials/campaign → campaign list (alias)
+routes.get("/campaign", (c) => {
+ const space = c.req.param("space") || "demo";
+ return c.html(
+ campaignFrame(`${RSOCIALS_PUBLIC}/campaigns`, space, `Campaigns | rSocials`)
+ );
+});
+
+// /rsocials/campaign/:id → specific campaign editor
+routes.get("/campaign/:id", (c) => {
+ const space = c.req.param("space") || "demo";
+ const id = c.req.param("id");
+ return c.html(
+ campaignFrame(
+ `${RSOCIALS_PUBLIC}/campaigns/${id}`,
+ space,
+ `Campaign Editor | rSocials`
+ )
+ );
+});
+
+type StatusCode = 200 | 201 | 204 | 400 | 401 | 403 | 404 | 409 | 500 | 502;
+
+export const rsocialsModule: RSpaceModule = {
+ id: "rsocials",
+ name: "rSocials",
+ icon: "\uD83D\uDCE2",
+ description: "Campaign strategy workflow builder",
+ routes,
+ standaloneDomain: "rsocials.online",
+};
diff --git a/server/index.ts b/server/index.ts
index eac52ac..4b318ef 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -63,6 +63,7 @@ import { inboxModule } from "../modules/inbox/mod";
import { dataModule } from "../modules/data/mod";
import { splatModule } from "../modules/splat/mod";
import { photosModule } from "../modules/photos/mod";
+import { rsocialsModule } from "../modules/rsocials/mod";
import { spaces } from "./spaces";
import { renderShell, renderModuleLanding } from "./shell";
import { syncServer } from "./sync-instance";
@@ -92,6 +93,7 @@ registerModule(inboxModule);
registerModule(dataModule);
registerModule(splatModule);
registerModule(photosModule);
+registerModule(rsocialsModule);
// ── Config ──
const PORT = Number(process.env.PORT) || 3000;