Open-Source Cold Email & Warmup Platform
Warmbly 是开源的冷邮件平台:温暖您的邮箱、发送活动、并对每条回复进行分流。可在我们的云端运行,也可自行托管整套系统。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Social media scheduling for agencies — Next.js, NestJS, PostgreSQL, Redis
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Social media scheduling for agencies. An agency plans its clients' posts on one calendar, the client approves them from a link, and each post goes out on Facebook and Instagram at its time - automatically. Captions are drafted by AI in the brand's own voice.
Status: phase 7 (launch) - ready to deploy: browser tests (Playwright: sign-up, login, language, a post to the calendar, the client's approval) in CI;
RUN_WORKER=trueruns the publish worker inside the API (one free service);/api/health/pingfor an uptime pinger that doesn't wake the database;render.yamland DEPLOY.md for Vercel + Render.Phase 6 (AI captions) - done. "Write me a caption" in the post editor: the idea in a few words -> three really different captions in the client's language and voice (from the client's settings), hashtags cleaned and after the text, within Instagram's limits; "improve the text I wrote" sends the draft too. Google Gemini through its REST API in JSON mode; the agency's words go to the model as quoted data, not instructions; at most 30 requests per person per hour. Nothing is saved until the person picks one.
Phase 5 (client approval) - done. A client can require approval (a setting on the client): then only posts the client approved can be scheduled. A draft is sent for approval (editors too); the owner or a manager makes a review link (14 days, kept as a hash) and sends it - copy or WhatsApp. The client opens it with no account, sees each post as it will look on Facebook and Instagram, and approves or asks for changes with a note, which the agency sees in the editor. Changing an approved post's text, platforms or files needs a new approval; a link reaches its own client only and can be stopped.
Phase 4 (publishing) - code done, waiting for the Meta app. Scheduling a post puts a delayed job on the publish queue (BullMQ) for its exact time - nothing polls the database. The worker takes it then, locks the post (PUBLISHING), and sends it: Facebook (text, a photo, several photos, a video) and Instagram (an image, a Reel, a carousel), images as JPEG and MOV as MP4 through Cloudinary. Each platform is recorded with its link or Meta's reason; a platform already out is never sent twice. Meta busy -> tried again after 1, 2, 4 minutes; refused for good -> FAILED with the reason; a refused token marks the page "needs connecting again". An hourly sweep re-queues any scheduled post whose job is missing. Website: "Publishing…", the reason a post failed, links to the published post.
Phase 3 (Facebook and Instagram) - code done, waiting for the Meta app. each client connects its Facebook page (and the Instagram professional account linked to it) through Facebook's login - a one-time
stateagainst forged answers, the person's pages listed to pick from, the page token stored encrypted (AES-256-GCM) and never sent to the browser,appsecret_proofon every Graph API call; a page can't be on two clients of one agency; check / reconnect / disconnect; scheduling needs each picked platform connected (once Meta is set up). Website: a "Facebook and Instagram" card on the client's page, the page picker, badges on the clients list, warnings in the post editor.Phase 2 (posts) - done. the posts table (draft -> scheduled -> published / failed), the posts API (calendar range, drafts with no time, create, edit, schedule, unschedule, delete; editors draft for their brands, the owner and managers schedule; Instagram's caption and hashtag limits checked before scheduling); website: the posts calendar (month and week, each post on its day in its client's time zone, a client filter, + on a day for a new post, drafts with no time beside it) and the post editor - client, text with Instagram's counters, platforms, a time in the client's own time zone, a live preview, save / schedule / unschedule / delete by role. Images and videos: in the editor, drop or pick files - each uploads with a progress bar, then can be moved or removed; a file Instagram would refuse is marked red; the preview shows the real files (Facebook grid, Instagram carousel) and the calendar a thumbnail. Behind it, the browser uploads straight to Cloudinary with a ticket the API signs for one file name of that client; the API then reads the file's real size and format from Cloudinary and attaches it (up to 10 per post, reorder, remove); Meta's rules checked before scheduling (Instagram needs a file, images 4:5 to 1.91:1, Reels 3 s - 15 min, 60 s in a carousel; Facebook: photos, or one video alone).
Phase 1 (accounts and agencies) - done (API and website). Done: database tables, agency sign-up, login (cookie), route protection and roles, brands (clients) with editor assignment, staff invites (link), staff list / role change / removal (API complete). Website: Arabic / English (language in the URL: /ar, /en), font, colors, base components, agency sign-up and login pages, a protected dashboard (top bar, sections, log out), clients pages (list, add, edit, editors, delete; read-only for managers and editors), team page (invite by link - copy or WhatsApp, open invites, role change, removal) and the invite page where a new staff member makes their account.
| Layer | Tech |
|---|---|
| Web | Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS 4 |
| API | NestJS 12, TypeScript |
| Database | PostgreSQL 17 + Prisma 7 |
| Jobs | Redis 7 + BullMQ |
| Tests | Vitest + Supertest, Playwright |
| CI | GitHub Actions |
Browser ──► Next.js (apps/web) ──/api/*──► NestJS API (apps/api) ──► PostgreSQL
│
└──► Redis ◄── Worker (apps/api, separate process) ──► Meta Graph API
The API and the worker are the same codebase but two processes: the API answers the website, the worker runs background jobs (publishing posts at their time). A slow or failing call to Meta can never slow the website down.
postflow/
├── apps/
│ ├── web/ Next.js site
│ │ └── src/
│ │ ├── app/[lang]/ pages, under /ar or /en
│ │ ├── i18n/ languages: dictionaries (ar.ts, en.ts), config
│ │ ├── lib/ api() - the one way to call the API; error texts; serverApi() / getSession() (server); time zones;
│ │ │ zoned-time.ts (a time in a client's zone <-> UTC); calendar.ts (the calendar's days);
│ │ │ caption.ts (Instagram's limits); media.ts (file rules, Cloudinary thumbnail URLs);
│ │ │ upload.ts (a file straight to Cloudinary, with progress); *.test.ts next to each (npm test -w web)
│ │ ├── components/ ui/ (Button, Input, Select, Textarea, FormError, LanguageSwitch), Logo, LocalDate, SystemStatus, MediaThumb
│ │ └── proxy.ts sends a URL without a language to /ar or /en; /dashboard needs a login
│ └── api/ NestJS API + worker
│ ├── prisma/ schema.prisma, migrations
│ ├── src/
│ │ ├── config/ environment variables, checked at start-up
│ │ ├── prisma/ database client
│ │ ├── redis/ Redis connection
│ │ ├── health/ GET /api/health
│ │ ├── auth/ sign-up, login, /me, logout; AuthGuard + @Public / @Roles / @CurrentUser
│ │ ├── brands/ an agency's clients: /api/brands, editors per brand
│ │ ├── invites/ staff invites: the owner makes a link, the person opens it and makes an account
│ │ ├── staff/ the agency's people: /api/staff, change a role, remove someone
│ │ ├── posts/ /api/posts: the calendar, drafts, scheduling, files; posts.rules.ts = what a post needs before it's scheduled
│ │ ├── meta/ Facebook login and pages: MetaGraph (what the API needs) and GraphApiMeta; token-cipher.ts
│ │ ├── ai/ caption ideas: CaptionWriter (the prompt, the cleanup) and GeminiCaptionWriter
│ │ ├── review/ client approval: review links and the client's answers (public, the link is the key)
│ │ ├── publish/ the publish queue (PublishQueue: delayed jobs) and PublisherService (one post -> Meta)
│ │ ├── media/ file storage: MediaStorage (what the API needs) and CloudinaryStorage (signed uploads, details, delete)
│ │ ├── common/ shared pieces (request validation)
│ │ ├── worker/ worker module: heartbeat, PublishWorker (takes jobs at their time, hourly sweep)
│ │ ├── main.ts API entry point
│ │ └── worker.ts worker entry point
│ └── test/ e2e tests (real database)
├── docker-compose.yml PostgreSQL + Redis for development
└── .github/workflows/ci.yml
Requirements: Node.js 24 (or Node 22 with npm install -g npm@11), and either Docker Desktop or the two free cloud services below.
npm install # installs both apps and generates the Prisma client
cp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.env.local
npm run db:up # PostgreSQL + Redis in Docker
# three terminals:
npm run dev:api # http://localhost:4000/api/health
npm run dev:worker
npm run dev:web # http://localhost:3000
The home page shows a live status of the API, database, Redis and worker.
Skip npm run db:up and use two free hosted services instead - nothing to install:
postgresql://...neon.tech/neondb?sslmode=verify-full) into DATABASE_URL in apps/api/.env.rediss://... URL into REDIS_URL.Neon's free database sleeps after 5 idle minutes; the first request after that takes a moment longer.
Post files are stored on Cloudinary (free plan: no card needed). In its dashboard press
Go to API Keys, copy the API environment variable (cloudinary://<api_key>:<api_secret>@<cloud_name>) and
paste it into CLOUDINARY_URL in apps/api/.env. Without it everything works except uploads. The e2e tests use a fake
store, so CI needs no account.
Get a key at aistudio.google.com -> Get API key (the free tier is enough) and put it
in GEMINI_API_KEY. Without it the "write me a caption" button says it isn't set up. GEMINI_MODEL defaults to
gemini-flash-latest, Google's newest Flash model.
Connecting clients' pages needs a Meta app (developers.facebook.com) with Facebook
Login for Business. Put its App ID and App secret in META_APP_ID / META_APP_SECRET, and add
http://localhost:3000/api/meta/callback to its Valid OAuth Redirect URIs. In development mode the app works on
the pages of its own admins and testers, with no review by Meta. Without the two keys connecting is off, and posts can
be scheduled without a connected page. The e2e tests use a fake Meta.
Upstash's free plan has a monthly command limit, which is plenty for development.
npm run format:check # Prettier
npm run typecheck -w api && npm run lint -w api && npm test -w api
npm run test:e2e -w api # needs `npm run db:up`
npm run typecheck -w web && npm run lint -w web && npm test -w web && npm run build -w web
npm run build -w api && npm run test:browser -w web # Playwright: needs the database + Redis running
CI runs all of them on every push and pull request.
AbdAlrhmn ALkhlwt - Full-Stack Developer · GitHub