Product & technical blueprint

Freight Forwarder Job Costing — from Excel margins to a per-shipment ledger

A two-document blueprint: what we're selling and how it works (for the commercial founder), and how it's built (for the technical founder) — for the single idea selected from the Malaysia opportunity scan: per-shipment job costing for SME freight forwarders and customs brokers.

Idea source Malaysia B2B opportunity scan, idea #4
Team 1 technical founder (solo build) · 1 commercial founder
Prepared 12 Oct 2026
DOCUMENT ABusiness & Product Overview — for the commercial founder
A·1

Product definition

Overview

A web app for freight forwarders and customs brokers that replaces the Excel sheet they currently use to work out whether a shipment actually made money. For every job, it captures what was billed and every cost against it — ocean/air freight, duty, handling, trucking, documentation fees — and shows the margin immediately, per shipment, per client, and per trade lane. It also holds the shipment's documents (Bill of Lading, Certificate of Origin, permits) in one place, with alerts before anything expires.

Problem being solved

A forwarder's margin on any one job depends on a dozen cost lines that arrive at different times from different parties (the shipping line's invoice, the trucker's invoice, port handling charges, customs duty). Today these get pasted into a shared Excel workbook, usually by whoever has time, and nobody can say with confidence which clients or lanes are actually profitable until someone reconciles everything at month-end — by which point the next quote on that same lane has often already gone out underpriced.

Target customers

SME freight forwarders and customs brokers in Malaysia with roughly 5–30 staff, running a steady volume of ocean/air shipments, who do not already run an enterprise TMS (Transport Management System) because it is priced and built for much larger operations. The buyer is the owner or operations manager — usually the same person who currently maintains the Excel workbook or directly supervises whoever does.

Main product functionality

User roles

Owner / Admin
Full access — manages company settings, staff accounts, billing, and sees all jobs and margin reports
Ops staff
Creates and manages shipment jobs, logs costs, uploads documents — sees jobs they created or are assigned to by default, with an option for the Owner to grant company-wide visibility

Assumption to validate: whether ops staff should see every job by default or only their own. Forwarders with a small, trusted team may want full visibility; larger ones may want it restricted. Confirm with the first pilot customer rather than deciding this up front.

Key business rules

Core value proposition

Know which shipments, clients, and lanes are actually profitable the moment the last cost comes in — not at month-end reconciliation, when it's too late to reprice the next quote.

Minimum Sellable Product (MSP)

One pilot forwarder can replace their Excel workbook for new shipments: create a job, log costs against it from multiple staff, see the margin, and store the job's documents with expiry reminders. Nothing more.

PriorityFeatureWhy this priority
P0Shipment job creation (client, billed amount, lane)The entire product is built around a job record
P0Cost line items against a job, multi-userThis is the Excel-replacement core
P0Live margin per jobThe core value proposition
P0Document upload (BL/CO/permit) with expiry date and reminderSecond-most requested pain in forwarder workflows; cheap to add once file storage exists
P0Owner/Admin vs. Ops staff rolesNeeded from day one — multiple staff log costs, only the owner should delete
P1Margin rollups by client and by trade laneValuable but not required to replace the Excel sheet for a single pilot
P1CSV import of historical shipmentsHelps onboarding once the MSP is proven wanted, not before
P1Quote templates seeded from past job cost structuresA natural next step, not needed to prove the core loop
P2Multi-currency cost entries with live FX conversionOnly needed once a pilot customer actually books non-MYR cost lines regularly
P2Client-facing shipment status portalA different product surface (client self-service), not core to the margin problem
P2Direct accounting-software sync (Xero/AutoCount)High integration cost for a benefit the MSP doesn't need to prove
A·2

Business-facing product overview

What we're selling
A web app where a forwarder logs every shipment's costs as they come in and sees the real margin immediately — plus a place to keep the shipment's paperwork so nothing expires unnoticed.
Who should buy it
Owners or operations managers at freight forwarders / customs brokers, roughly 5–30 staff, who currently track shipment profit in a shared Excel sheet.
Problems it solves
Not knowing a shipment's real margin until weeks after it closes; costs scattered across emails and individual spreadsheets; documents expiring without warning.
How it works
Create a job for a shipment, enter what was billed. As invoices come in from the shipping line, trucker, customs agent, log each one against the job. The margin updates on its own. Upload the shipment's documents to the same job — the system reminds the team before any of them expire.
Main selling points
See profit the moment a shipment closes, not at month-end. Every staff member can log costs without emailing a spreadsheet around. One place for shipment documents with built-in expiry reminders.
What's included in release 1
Shipment jobs, multi-user cost logging, live margin, document storage with expiry reminders, owner/staff roles.
What's NOT included yet
Margin reports rolled up by client or lane (coming after the first pilots), multi-currency cost entries, a client-facing tracking portal, and direct sync with accounting software.
Customer onboarding
A short call to set up the company account and invite staff. New shipments go straight into the system from day one; old shipments are not migrated for the pilot — we start clean going forward rather than re-keying history.
Expected delivery timeline
8–10 weeks to a pilot-ready MVP from the day the first pilot forwarder commits — this assumes one developer working on this alone, with no major surprises in document-storage or file-upload handling; see §B·4 for the week-by-week breakdown and what could push this out.
Important limitations
The pilot only tracks shipments entered after go-live — it does not reconstruct historical profit. It is single-company per account; it does not yet connect to the forwarder's existing accounting software, so cost entries are typed in, not pulled automatically from invoices.

For pitching: lead with the month-end problem — "you find out a shipment lost money three weeks after you could have done anything about it." The document-expiry reminder is a secondary hook, not the opener; margin visibility is what justifies the subscription price.

A·3

Product workflows

A. Complete customer journey

flowchart LR
    A["Forwarder owner hears
about the product"] --> B{"Currently tracking
margin in Excel?"} B -- "Yes, in pain" --> C["Books a demo"] B -- "No real pain" --> X["Not a fit — exit"] C --> D["Sees live-margin demo
on a sample shipment"] D --> E{"Agrees to pilot?"} E -- "No" --> X E -- "Yes" --> F["Setup call:
company + staff invited"] F --> G["Team logs NEW shipments
going forward"] G --> H{"Margin visibility
actually used weekly?"} H -- "No" --> R["Churn risk —
founder follow-up"] H -- "Yes" --> I["Converts to paid
monthly subscription"] I --> J["Renews month over month"]
Fig. A — the sales motion hinges on one proof point: does the forwarder actually open the margin view weekly once shipments are in it.

B. Customer onboarding

flowchart TD
    A["Pilot agreement signed"] --> B["Owner creates
company account"] B --> C["Owner invites ops staff
by email"] C --> D{"Staff accepts invite?"} D -- "No, after 3 days" --> E["Owner re-sends
or removes invite"] D -- "Yes" --> F["Staff sets password,
lands on empty dashboard"] F --> G["Owner or staff creates
first real shipment job"] G --> H["Team logs costs on it
as invoices arrive"] H --> I["First margin view
shown — activation moment"]
Fig. B — no historical-data migration step: the pilot starts clean on new shipments, which is the fastest path to the activation moment in H.

C. Core product operation — working a shipment

flowchart TD
    A["Create job:
client, billed amount, lane"] --> B["Job appears in
open-jobs list"] B --> C["Any staff member logs
a cost line item"] C --> D["Margin recalculates
immediately"] D --> E{"More invoices
still expected?"} E -- "Yes" --> C E -- "No" --> F["Owner marks
job closed"] A --> G["Upload BL / CO / permit
to the job"] G --> H{"Document has
an expiry date?"} H -- "Yes" --> I["Reminder scheduled
14 days before expiry"] H -- "No" --> J["Stored, no reminder"] I --> K{"Renewed before
expiry?"} K -- "No" --> L["Overdue flag shown
on job"] K -- "Yes" --> J
Fig. C — the cost-logging loop (left) and the document-expiry loop (right) run independently against the same job record.

D. User roles and permissions

flowchart TD
    Owner["Owner / Admin"] -->|"full access"| Settings["Company settings,
billing, staff accounts"] Owner -->|"full access"| AllJobs["All shipment jobs
and margin reports"] Owner -->|"only role that can"| Delete["Delete a job or
a cost line item"] Staff["Ops staff"] -->|"create / edit own"| OwnJobs["Jobs they created
or are assigned to"] Staff -.->|"optional, owner-granted"| AllJobs Staff -->|"cannot"| Delete
Fig. D — deletion is Owner-only by rule (see §A·1); company-wide job visibility for staff is an optional grant, not the default (flagged as an assumption to validate).

E. Subscription and payment process

flowchart TD
    A["Pilot period ends
(target: 4 weeks)"] --> B{"Owner decides
to subscribe?"} B -- "No" --> X["Account frozen,
data kept 30 days"] B -- "Yes" --> C["Setup fee invoiced
(RM2,000 one-time)"] C --> D["Monthly plan selected
by shipment volume"] D --> E["Recurring card or
FPX charge via payment gateway"] E --> F{"Payment succeeds
each cycle?"} F -- "No" --> G["Grace period 7 days,
owner notified"] G --> H{"Resolved in
grace period?"} H -- "No" --> X H -- "Yes" --> F F -- "Yes" --> I["Account stays active"]
Fig. E — a 7-day grace period on failed payment avoids cutting off an active forwarder mid-shipment over a card decline.

F. Administrative workflow

flowchart TD
    A["Owner opens
Company Settings"] --> B["Invite / remove staff"] A --> C["View billing &
invoice history"] A --> D["Set expiry reminder
lead time (default 14 days)"] A --> E["Export all shipment
data as CSV"] B --> F{"Staff removed
mid-pilot?"} F -- "Yes" --> G["Their logged jobs
reassigned to Owner,
audit trail kept"]
Fig. F — removing a staff member never deletes their cost entries, only reassigns ownership, preserving the audit trail required by the business rule in §A·1.
A·4

Roadmap & founder responsibilities

StageTechnical founderCommercial founder
DiscoveryConfirm document upload/expiry and multi-user cost entry are technically straightforward (they are — see §B·1)Interview 3–5 forwarders: do they actually re-key shipment data into a new system, or resist it? This is the key risk (see callout below)
PlanningLock the MSP scope in §A·1; resist adding lane/client rollups before a pilot asks for themConfirm pricing (setup RM2,000 + RM299–599/mo) survives a real pitch conversation
DevelopmentBuild per §B·4's phased planSecure one committed pilot forwarder before week 4, so real data exists to test against
TestingFix issues surfaced by the pilot's real shipment data — not synthetic test dataSit with the pilot's ops staff while they log a real cost entry; watch where they hesitate
LaunchDeploy, monitor for the first month, keep the document-reminder job running reliablyConvert the pilot to paid, then approach FMFF-connected forwarders for the next 4
Commercial assumption that must be validated before building: unvalidated forwarders are relationship-driven and slow to switch systems — the biggest risk to this idea is not technical, it's whether ops staff will actually re-key shipment data into a new tool rather than keep using the Excel sheet alongside it out of habit. Validate this with at least 2–3 forwarders actually agreeing to try logging real shipments before investing the full 8–10 week build — a forwarder who will not commit to that during discovery is unlikely to use the finished product either.
DOCUMENT BTechnical Blueprint — for the technical founder
B·1

System architecture

A modular monolith on managed infrastructure. Nothing here needs microservices, queues, or a separate background-job platform — the only asynchronous work is scheduled expiry-reminder checks, which a cron-style scheduled function handles.

Recommended stack

Frontend + backend
Next.js (App Router)
One codebase, one deploy, server actions remove the need for a separate API layer for most operations
Database
Postgres via Supabase or Neon
Managed, relational fits the job/cost-line/document model naturally, built-in auth if using Supabase
ORM
Drizzle
Typed schema and migrations without a heavy abstraction layer
Auth
Supabase Auth or Auth.js
Email/password is sufficient at this scale — no need to build auth from scratch
Document storage
Cloudflare R2 or Supabase Storage
S3-compatible object storage with signed URLs for the BL/CO/permit vault — no egress fees on R2
Scheduled jobs
Vercel Cron or Supabase Edge Functions on a schedule
Daily check for documents approaching expiry — no separate job queue needed at this volume
Payments
CHIP or Stripe
CHIP for local FPX/card rails at MY-friendly fees; Stripe if the forwarder ever pays in other currencies
Hosting
Vercel
Matches Next.js deploy model, generous free tier through the pilot phase

This is a deliberate match to a solo developer's existing toolkit rather than a from-scratch evaluation — swap any row for an equivalent the developer already knows well; the architecture below does not depend on the specific vendor.

flowchart TD
    subgraph Client["Browser"]
        UI["Next.js UI
(dashboard, job, documents)"] end subgraph App["Next.js app (Vercel)"] SA["Server actions /
API routes"] CRON["Scheduled function:
daily expiry check"] end subgraph Data["Managed data layer"] DB[("Postgres
Supabase / Neon")] STORE[("Object storage
R2 / Supabase Storage")] end EMAIL["Email provider
(Resend)"] PAY["Payment gateway
(CHIP / Stripe)"] UI -->|"reads/writes"| SA SA -->|"query/mutate"| DB SA -->|"signed upload/download URL"| STORE UI -->|"direct upload via
signed URL"| STORE CRON -->|"query expiring docs"| DB CRON -->|"send reminder"| EMAIL SA -->|"checkout / webhook"| PAY PAY -->|"payment status webhook"| SA
Fig. G — the browser uploads documents directly to object storage using a signed URL issued by a server action, so large files never pass through the app server.

Security

Monitoring & backups

B·2

Database & API design

Single-tenant-per-row via company_id — not a separate schema per tenant. A company has many users and many clients; a client has many shipment jobs; a job has many cost line items and many documents.

erDiagram
    COMPANY ||--o{ USER : employs
    COMPANY ||--o{ CLIENT : "has as customer"
    CLIENT ||--o{ JOB : ships_for
    JOB ||--o{ COST_LINE : "costed by"
    JOB ||--o{ DOCUMENT : "attached to"
    USER ||--o{ JOB : creates
    USER ||--o{ COST_LINE : logs

    COMPANY {
        uuid id PK
        text name
        text subscription_status
        timestamptz created_at
    }
    USER {
        uuid id PK
        uuid company_id FK
        text email
        text role "owner | staff"
        timestamptz invited_at
        timestamptz joined_at
    }
    CLIENT {
        uuid id PK
        uuid company_id FK
        text name
        text contact_email
    }
    JOB {
        uuid id PK
        uuid company_id FK
        uuid client_id FK
        uuid created_by FK
        text lane
        numeric billed_amount
        text status "open | closed"
        timestamptz created_at
        timestamptz closed_at
    }
    COST_LINE {
        uuid id PK
        uuid job_id FK
        uuid logged_by FK
        text category "freight | duty | handling | trucking | other"
        numeric amount
        text note
        timestamptz created_at
    }
    DOCUMENT {
        uuid id PK
        uuid job_id FK
        uuid uploaded_by FK
        text doc_type "BL | CO | permit | other"
        text storage_key
        date expires_on
        timestamptz reminder_sent_at
        timestamptz created_at
    }
Fig. H — every table below COMPANY carries (directly or via JOB) a path back to company_id, which is what every query filters on first.

Key constraints

API endpoints

EndpointMethodAuthNotes
/api/jobsPOSTAny authenticated user in companyBody: client_id, lane, billed_amount. Returns created job
/api/jobsGETAny authenticated user in companyQuery by status/client; staff sees own unless granted company-wide view
/api/jobs/:idGETOwner, or staff with access to this jobReturns job with cost_lines and documents joined, margin computed server-side
/api/jobs/:idPATCHOwner, or job creatorUpdate lane/billed_amount/status
/api/jobs/:idDELETEOwner onlyHard restriction enforced server-side per business rule
/api/jobs/:id/costsPOSTAny authenticated user with job accessBody: category, amount, note
/api/jobs/:id/costs/:costIdDELETEOwner onlyHard restriction enforced server-side per business rule
/api/jobs/:id/documents/upload-urlPOSTAny authenticated user with job accessReturns a short-lived signed PUT URL to object storage
/api/jobs/:id/documentsPOSTAny authenticated user with job accessRecords doc_type, storage_key, expires_on after client-side upload completes
/api/company/staff/invitePOSTOwner onlySends invite email, creates pending USER row
/api/billing/webhookPOSTPayment gateway signatureUpdates COMPANY.subscription_status on payment success/failure
B·3

UI/UX planning

Sitemap

Dashboard structure

A single table of jobs (open by default, filterable to closed), columns: client, lane, billed amount, costs logged so far, margin, status. Margin is colored — green when positive, amber when thin, red when negative — so a forwarder scans the list and spots a problem job without opening it.

Screen-by-screen functionality

ScreenKey actionsEmpty stateError state
DashboardOpen a job, start a new job"No shipments yet — create your first job" with a prominent buttonFailed to load jobs: retry button, not a blank screen
Job detailLog a cost, upload a document, close the jobNo cost lines yet: "Margin will show once costs are logged" instead of showing RM0 misleadinglyDocument upload failure shows the specific reason (file too large, unsupported type) not a generic error
New jobPick client, enter lane and billed amountNo clients yet: inline "add a client" without leaving the formValidation errors inline per field, not a toast summary
Settings → StaffInvite, remove, view pending invites"Just you so far — invite your team" Invite-send failure names the reason (bad email, already invited)

Visual style

Modern, minimal, professional — not playful. This is a tool used by ops staff mid-task, often on a shared desktop. Favor a dense, scannable table-first layout over cards; avoid heavy illustration or marketing-style visuals inside the app itself. Keep to one accent color for interactive elements and status color (margin health) kept visually distinct from that accent.

Responsive requirements

Desktop-first — ops staff work from a desk when logging costs and reviewing documents. A usable mobile view for checking margin on the go is a reasonable P1, not a P0 requirement; don't invest in a bespoke mobile layout before a pilot customer asks for one.

B·4

Development roadmap

Sized for one developer. The goal is a pilot forwarder logging real shipments by week 8–10, not a feature-complete platform.

Phase 1 — FoundationWeeks 1–2

Objective: auth, company/user model, and the job + cost-line data model working end to end with no UI polish.

Build: Next.js project scaffold, Postgres schema per §B·2 (company, user, client, job, cost_line tables), auth (signup/login/invite), create-job and log-cost server actions.

Dependencies: none — this is the starting point.

Deliverable / done-when: a developer can create a company, invite a second user, create a job, and log multiple cost lines against it from both accounts, with margin computed correctly.

Phase 2 — Document vaultWeeks 3–4

Objective: upload, store, and track expiry for job documents.

Build: object storage bucket, signed-URL upload flow, document table + UI on the job detail screen, scheduled daily function that checks for documents approaching their expiry date and sends an email reminder.

Dependencies: Phase 1's job model.

Deliverable / done-when: a document uploaded with an expiry date 15 days out triggers a reminder email the next day the cron runs (test with a short lead time locally).

Phase 3 — Dashboard & pilot-ready polishWeeks 5–6

Objective: the dashboard, empty/error states from §B·3, and enough visual polish that a real forwarder can use this without hand-holding.

Build: dashboard job list with margin coloring, job detail screen layout, settings screen (staff invites, reminder lead time), responsive-enough desktop layout.

Dependencies: Phases 1–2.

Deliverable / done-when: the technical founder can walk through creating a job, logging 3 cost lines, uploading a document, and seeing the margin — entirely through the UI, no admin/database shortcuts.

Phase 4 — Pilot onboarding & billingWeeks 7–8

Objective: onboard the first real pilot forwarder and wire up payment collection for when the pilot converts.

Build: payment gateway integration (setup fee + recurring subscription), billing status on the company record, pilot forwarder's actual company/staff set up in production.

Dependencies: Phase 3 complete and stable; a committed pilot forwarder (commercial founder's responsibility, should already be lined up by this point).

Deliverable / done-when: the pilot forwarder's team is logging real shipments in production, and a test payment can be processed end to end.

Phase 5 — Stabilize on real usageWeeks 9–10

Objective: fix whatever the pilot's real usage actually surfaces — this phase is reactive by design, not a fixed feature list.

Build: whatever the pilot's first 1–2 weeks of real shipments reveal as broken or confusing. Likely candidates based on the product shape: cost-category list needs adjusting, margin display needs a "pending costs expected" indicator, document type list needs a category the pilot actually uses.

Dependencies: a pilot actively using the product in Phase 4.

Deliverable / done-when: the pilot forwarder has used the product for at least 2 consecutive weeks of real shipments without reverting to the Excel sheet.

This 8–10 week estimate assumes no major surprises in the signed-URL upload flow (the one genuinely new technical surface here) and a pilot forwarder already lined up before Phase 4 starts. If no pilot is secured by week 6, pause new feature work and treat that as the actual blocker — building further without a real user logging real shipments just produces more unvalidated guesses about cost categories and document types.