Skip to main content

Social Media Marketing (SMM)

The Kapaldo SMM system lets an operator generate AI-written Facebook posts (with branded images), queue them for admin review, and have a local agent post approved content automatically — without ever storing a Facebook password.


Architecture

smm/ agent ayts-api (Cloudflare Workers) ayts-admin
───────────────── ──────────────────────────── ──────────────────
npm run generate-drafts ──► POST /api/smm/posts ──────────────► Admin sees drafts
at /admin/smm
↓ clicks Approve
npm run poll ◄─────────── GET /api/smm/posts/approved ◄──────── status = approved
│ (X-SMM-Key header)
│ Playwright logs into Facebook
│ posts to page / groups
└─► PATCH /api/smm/posts/:id/status (posting → posted / failed)

The agent (smm/ directory) is a Node.js/TypeScript project that runs locally or on a server you control. It never runs in the cloud. Facebook credentials stay on your machine.


Components

1. generate-drafts script

Path: smm/src/generate-drafts.ts
Run: npm run generate-drafts (from smm/)

Uses Google Gemini to write:

  • A page text post
  • A group text post
  • A vendor recruitment card (image + caption)
  • Branded media cards (launch / how-it-works / vendor / community)

Uploads images to R2 via the API, then calls POST /api/smm/posts to create drafts in the database. Does not require Facebook login.

2. Admin review UI

Path: ayts-admin/app/admin/smm/page.tsx
URL: admin.kapaldo.com/admin/smm

Admins see all draft posts with image previews, approve or delete them. The page is under "Marketing → Social Media" in the sidebar.

Features:

  • Filter tabs: All / Draft / Approved / Posting / Posted / Failed
  • Compose modal: write a custom post, choose target (page / groups / both)
  • Config modal: edit FB page URL, group URLs per location

3. poll daemon

Path: smm/src/poller.ts
Run: npm run poll (from smm/)

Polls GET /api/smm/posts/approved every 30 seconds. When approved posts are found, it:

  1. Marks the post posting
  2. Launches a Playwright browser (headed or headless)
  3. Logs into Facebook (using saved session cookies — password only prompted if session expired)
  4. Posts to the page and/or target groups
  5. Marks the post posted or failed

4. API routes

File: ayts-api/src/routes/smm.ts
Base path: /api/smm

MethodPathAuthPurpose
GET/postsAdmin JWTList all posts
POST/postsAdmin JWT or X-SMM-KeyCreate draft
PATCH/posts/:id/approveAdmin JWTApprove a draft
DELETE/posts/:idAdmin JWTDelete draft or failed post
GET/posts/approvedX-SMM-KeyAgent polls for approved posts
PATCH/posts/:id/statusX-SMM-KeyAgent updates post status
GET/configAdmin JWTRead FB config
PUT/configAdmin JWTSave FB config

The X-SMM-Key is a shared secret stored as a Cloudflare Workers secret (SMM_API_KEY).


Database Tables

smm_posts

ColumnTypeDescription
idUUIDPrimary key
contentTEXTPost body
image_urlTEXTOptional image URL
targetTEXTpage, groups, or page_and_groups
post_typeTEXTtext or image
statusTEXTdraftapprovedpostingposted / failed
error_textTEXTFailure reason if failed
approved_byUUIDAdmin user ID
approved_atTIMESTAMPTZ
posted_atTIMESTAMPTZ
created_atTIMESTAMPTZ

smm_config

Key-value store for runtime configuration (managed via the admin UI):

KeyDescription
fb_emailFacebook login email
fb_page_urlURL of the Kapaldo Facebook page
fb_page_nameDisplay name for content generation
lagonglong_groupsComma-separated group URLs
salay_groupsComma-separated group URLs
claveria_groupsComma-separated group URLs

Security

  • Facebook password is never stored. The poller prompts for it via a hidden CLI input (readline with raw-mode echo suppression) only when the saved session cookie has expired.
  • Session cookies are persisted to smm/session/fb-session.json between runs.
  • The agent authenticates to the API with X-SMM-Key (a long random secret), never with admin credentials.
  • Only draft and failed posts can be deleted; posted posts are permanent audit records.

Setup & Configuration

1. Set the API key in Cloudflare Workers

cd ayts-api
npx wrangler secret put SMM_API_KEY --env production
# Enter any long random string (32+ characters) when prompted

2. Configure smm/.env

FB_EMAIL=your-facebook-email@gmail.com
# FB_PASSWORD is NOT stored here — prompted at runtime when session expires

FB_PAGE_URL=https://www.facebook.com/kapaldo05
LAGONGLONG_TARGET_GROUPS=https://www.facebook.com/groups/...,...
SALAY_TARGET_GROUPS=https://www.facebook.com/groups/...,...
CLAVERIA_TARGET_GROUPS=https://www.facebook.com/groups/...,...

GEMINI_API_KEY=your-gemini-key
KAPALDO_LOCATION=Claveria, Misamis Oriental
KAPALDO_MESSENGER_URL=https://m.me/kapaldo05
KAPALDO_STORE_URL=https://kapaldo.com

SMM_API_URL=https://ayts-api-prod.jerquinbayudo.workers.dev
SMM_API_KEY=same-value-you-set-in-wrangler-above

3. Daily workflow

# Step 1 — Generate AI content and push as drafts (no FB login needed)
cd smm && npm run generate-drafts

# Step 2 — Admin reviews and approves at admin.kapaldo.com/admin/smm

# Step 3 — Run the poller to execute approved posts
cd smm && npm run poll
# Enter FB password if prompted (session expired)

Pending / Known Issues

  • SMM_API_KEY must be reconfigured — the deploy script generated a random key that was not saved. Run wrangler secret put SMM_API_KEY --env production with a known value and add it to smm/.env.
  • Stray worker ayts-production — created accidentally during secret setup. Delete it from the Cloudflare dashboard (Workers & Pages → ayts-production → Delete).
  • No cron / scheduler — the poller must be started manually. Consider running it as a background process with pm2 or a systemd service on a persistent machine.
  • Image upload in generate-drafts — the uploadImage() function in smm/src/api-client.ts needs the R2 upload endpoint to accept file paths, not just multipart blobs. Verify the endpoint works end-to-end before the first run.
  • Group posting rate limiting — Facebook may throttle or flag accounts that post to many groups in quick succession. The ACTION_DELAY_MS env var (default 3000ms) controls the delay between posts — increase it if the account gets restricted.