PLANLISH
Getting Started

Getting started

Prerequisites

  • Node.js 22 or newer.
  • pnpm 11.
  • PostgreSQL reachable through DATABASE_URL and DIRECT_URL.
  • Provider credentials only for integrations you intend to exercise.

Configure the environment

Copy .env.local.example to the repository's untracked environment file and replace empty values with your own credentials. Never commit secrets. Use independent, random values for Better Auth, social credential encryption, and integration credential encryption.

Run the core validator while developing:

pnpm validate:env

Run the full validator before a production deployment:

pnpm validate:env:full

Prepare the database

Generate the Prisma client:

pnpm --dir packages/database generate

Schema synchronization alone does not install the SQL triggers required by publishing and media cleanup. For an existing database, follow packages/database/prisma/workspace-migration/README.md: stop writers, create a verified backup, run the ordered migration scripts, and execute every documented smoke check. The repository's docs/backup-and-restore.md describes a consistent backup and a restore into a separate, empty verification database. Never use a production database as an automated-test fixture.

For a new, empty loopback test database named planlish_e2e, set TEST_DATABASE_URL explicitly and run pnpm --filter @repo/scripts bootstrap:test-db. This installs the current Prisma schema and runtime SQL, uses UTC, and refuses to overwrite a populated database. CI uses its own PostgreSQL service.

Run PLANLISH

pnpm dev

The SaaS application runs on the configured NEXT_PUBLIC_SAAS_URL. Registering the first user creates a personal Better Auth Organization and redirects to /\{workspaceSlug\}/calendar.

Connect providers only after their callback URLs, scopes, and credentials are configured. PLANLISH does not return mock success for missing provider setup.

Workspace subscription access

Production workspace RPC requests require an active subscription belonging to the workspace owner. With the configured user billing, invited members use the owner's subscription; they do not need to buy a separate subscription to join the workspace. App entry and plan selection check that same owner. If access expires, the owner can select a plan, while members see instructions to contact the owner. Authenticated payment and account recovery endpoints remain available.

Local development does not require a paid subscription. Missing or unknown subscription statuses never count as active; production access is limited to active, trialing, or provider-cancelled subscriptions whose verified end date is still in the future. A missing or elapsed cancellation end date does not grant access. Existing cancelled purchases require provider reconciliation before migration 29's application rollout; see the workspace migration guide.

Failed media processing

The media library shows a failure message when a file is rejected, cannot be queued, or exhausts its processing attempts. These files have no usable preview and cannot be selected for publishing. Remove the failed file using its trash button and upload a supported replacement. Restoring it from trash preserves the failure; it does not bypass security checks. Existing terminal records can be repaired with workspace migration 27. Deploy the updated media-processing task to enable final-attempt and failure-hook state recording.

Drafts and media selection

Draft changes are automatically saved after two seconds without another edit. An unchanged draft does not create additional revisions. If automatic saving fails, your content stays in the editor; use Save draft to retry, or continue editing to trigger a new automatic save.

Clearing platform-specific text or its custom media selection returns that platform to the shared post content and media. Saving the draft preserves this reset and keeps unrelated platform options.

The post composer and media pickers search all folders. Using a file from the library opens it directly in the composer; it must be ready and belong to the current workspace.

Media used in posts, platform-specific selections, products or brand logos must be detached before it can be moved to trash. If some selected files cannot be removed, they stay selected for review. Permanent cleanup protects existing references and retries storage failures in the background.

Upload and restore failures

A failed upload does not stop the remaining files. The library lists failed files and lets you retry them without uploading the successful files again. Retries retain the original destination folder. This retry list lasts while the page is open; it is not a persistent upload history.

Bulk trash and restore actions wait for each selected file. Successful files leave the selection; failed files remain available for another attempt. The bulk action button stays disabled until the operation finishes.

Media previews and storage capacity

Use the edit button next to a folder to rename it or delete an empty folder. Folders containing files or subfolders cannot be deleted through this action. If the operation fails, the dialog keeps the entered name so you can retry after checking access and folder contents.

Select files and choose Move selected to browse destination folders. Use the path buttons to return to an ancestor or the root, load more folders when needed, then choose Move here. Each move accepts up to 200 files. Successful files leave the selection; a failed move keeps the selection so you can review access and retry.

Use Move folder beside a folder to move it and its contents to another parent or the root. The destination browser hides the folder being moved. The server also rejects destinations inside its subtree, including changes made by another user while the dialog is open.

Newly processed images keep their original file and store a separate JPEG preview for the media library. Image previews fit within 1024 × 1024 pixels without enlarging the original; video sample frames fit within 640 × 640 pixels. Preview bytes count toward storage usage. Older images without a preview continue to use their original file in the library until they are processed again.

Video processing includes the normalized video and its thumbnail in storage usage. If the output needs more space, available capacity includes outstanding uploads across the owner's workspaces. When capacity is insufficient, the file does not become ready for publishing. Free unused storage or change the plan before trying again. Processing records cleanup intent before writing output files. After an abrupt worker shutdown, unused output files remain eligible for background cleanup; files adopted by a ready library item remain protected. Deploy migration 32 and the matching workers together.

External import readiness

Use Load more to browse additional Canva designs or stock search results. If loading fails, retry from the media panel; previously loaded results remain available. Stock search supports up to 100 pages per search. Changing the provider or search text selects that search's own results.

Stock file downloads are limited to 500 MB and three minutes. A download exceeding the byte limit is stopped before the file is added to your library.

The import history also offers Load more for older jobs. Its processing counter includes active imports across the workspace, including older pages. History keeps refreshing while that count is positive. If a history page fails to load, retry it while keeping the jobs already displayed.

Imports require current workspace content-creation access when processing starts and again before downloaded files are stored. Removing a member, reducing their role, or ending required subscription access can stop a queued import. Storage is attributed to the current workspace owner.

Canva exports can take several minutes. Each attempt checks export status up to 60 times, waiting ten seconds between checks, and resumes a saved export after a temporary failure. Each metadata or export request has a 30-second deadline; downloading a file, including its body, has a three-minute deadline. Network time adds to the polling waits. A failed or unavailable export is reported as an import failure instead of silently creating another export.

Drive and Canva imports retain their stored file if the processing queue is temporarily unavailable.

Once the queue confirms an import, a later activity-log or run-tracking error does not turn that accepted import into a failed submission. Follow its status in import history.

An automatic retry resumes the same file without downloading or charging storage again. Import history continues to show processing until the output is active; rejected or missing output is shown as failed. Replaying an already completed import does not recreate a file that was intentionally removed.

Managing an existing subscription

If checkout reports that a subscription already exists, use the subscription management button shown with the message. It opens the billing portal for that purchase after checking ownership. This also applies to paused, overdue, or cancelled subscriptions that have not been recorded as expired. Resolve the existing subscription before starting another checkout. An expired subscription can start a new checkout. Simultaneous first checkouts and previously issued checkout links still require additional protection against duplicate subscriptions.

Bulk scheduling results

If a bulk post was saved but queue delivery cannot be confirmed, the editor keeps the saved post and shows a calendar review link. That row is removed from the remaining batch so submitting the remaining rows does not create another copy. Review each linked post before retrying: some platform targets may already be queued. The notice remains available during the current editor session; saved posts remain in the calendar. The publishing dispatcher recovers missing queue deliveries from durable intent after migration 31 and its workers are deployed. Migration 34 adds safe recovery of expired preparation work.

Review a publishing target

Open Publish queue to review current attempts and open the original post. Editors with access to the account can retry a failed target, cancel it, or choose a new time for that target. Other accounts that already published keep their result. A target with its own date appears at that time in the calendar; moving the entire post resets those individual dates.

If delivery is uncertain, check the provider before confirming whether the post was published. Record the provider post ID when confirming publication. Confirming that it was not published does not send it immediately; retry is a separate action. The app refuses changes while a running delivery cannot be confirmed stopped. This prevents an old worker and a new attempt from publishing the same content.

The calendar loads the complete selected period in bounded pages. Account, status, brand, author and platform filters are applied by the server. A failed page shows a retry notice instead of presenting an incomplete period as complete.

Recover from a loading error

World clock, brand and transcript screens show an error and a retry action when data cannot be loaded. Shared clocks remain locked until saved preferences have loaded successfully, so an early edit cannot replace the workspace's saved cities. A failed later page preserves the rows already on screen.

Report access and regeneration

Workspace approvers, admins and owners can pause a report schedule from the saved schedules list. Paused schedules remain visible with a paused label. The worker checks the schedule before generating a report and before each new email delivery. An email already being sent cannot be recalled; a report generated before the pause may remain in report history.

To resume a paused schedule, choose a future start time in the displayed time zone and select Resume. The new time becomes the schedule's recurrence anchor; missed paused occurrences are not sent as a backlog. Current plan, social account and logo access are checked again. The member resuming the schedule becomes responsible for its future runs, whose access is checked again during generation. If the plan changed while the form was open, refresh the list before trying again. A worker processing an older schedule version cannot overwrite the new next-run time after finishing its deliveries.

Choose Edit on a saved schedule to load its title, format, accounts, branding, recipients and frequency into the report form. Choose a future next-delivery time and save changes. The displayed user/workspace time zone determines the new recurrence. Editing preserves the active or paused state; use Resume separately to activate a paused plan. Turn off Include branding to remove saved branding. Cancel editing exits edit mode without changing the saved schedule. If another member or worker changed the schedule, reload it before saving again. Existing schedules remain visible for pausing or deleting when the plan no longer includes scheduled email.

Report generation, downloads and share links check current workspace membership, plan features, account assignments and the permitted analytics history. If an account is removed or the plan no longer includes the report's dates or features, create a new report using the available scope or ask the workspace owner to restore access.

Older reports created with all accounts may not have a verifiable account list. Generate a new report if one of these files is unavailable. Existing viewer members can read permitted reports without needing permission to create scheduled reports.

Public share links are checked again when opened. Their storage redirects last 60 seconds; a storage URL already issued can remain usable until it expires. New scheduled report emails use application share links that expire after seven days. Opening one rechecks the report creator's current role, subscription, scheduled-email entitlement and report account scope before issuing a 60-second storage redirect. These checks remain in force if the schedule is deleted. The stored share can also be revoked from report history. Direct storage URLs sent by older versions remain usable until their original expiry.

Audit and report CSV exports protect formula-like text with a tab prefix inside the cell. This prefix is part of the data; stripping it before opening the file in a spreadsheet removes that protection.

AI requests

Open AI history in the composer to browse earlier text and image jobs for the current workspace. Filter by status, output type or provider, load older jobs, and reuse ready output. Generated images become selectable only after media processing succeeds. Partially saved output remains attached to the original job and is recovered without repeating the paid generation request.

AI settings distinguish a saved key from a verified connection and show the last check time. Changing credentials clears the old verification result. A provider without a connected generation tool is identified in settings even if its credentials can be stored.

Queued text jobs retain the model selected when the job was created and use the current API key. Changing the default model affects newly created jobs.

Connection checks have a 30-second request limit, text generation and profile suggestions have a 120-second limit, and image generation has a 300-second limit per provider request. A local timeout does not prove that the provider cancelled generation or billing. Check the job and provider outcome before manually repeating a paid generation whose result is uncertain. Interrupted jobs are recovered automatically only when another provider call is known to be safe. If a paid request may already have started, the job requires review instead of silently generating and billing again. Deploy migration 33, both generation workers and the recovery task together.

Dashboard totals

The dashboard counts all assigned social accounts and aggregates publishing performance across its reporting period without the former account or post row caps. Published posts use their publication date; failed posts use their last update date. Editing an older published post does not count it as a new publication. Daily performance follows the dashboard timezone and covers today plus the previous 29 local calendar dates, including the complete first day and today's activity so far.

If a dashboard section cannot be loaded, unavailable values are shown instead of zero totals. Use Try again to reload failed sections while keeping available dashboard data visible.

Notification history

Open the notification bell and choose Load older notifications to browse history in pages of 25. If loading fails, retry from the panel; loaded notifications remain visible. Mark all as read applies to your unread notifications across the full history, including pages you have not loaded.

If marking notifications as read fails, the panel shows an error and a retry action. Automatic marking pauses after a failure. Switching accounts closes and resets the notification panel.

On this page