Open-Source Cold Email & Warmup Platform
Warmbly는 메일함을 예열하고 캠페인 전송·답변 triage를 지원하는 오픈소스 콜드 이메일 플랫폼입니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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