Even with an ordering system already in place, a third of trade customers still send their orders over KakaoTalk. Adopting the system didn't solve it. From the customer's side, the chat app is already open; the system needs a login.
So the approach changed. Instead of persuading customers to use the system, we automated taking the orders exactly as they arrive in chat and filing them. The way orders are placed stays; only the retyping disappears.
First — what this case cannot do
Starting honestly. We could not find an official route for an outside system to receive inbound KakaoTalk messages in real time. Kakao's official business-messaging documentation covers sending only, and even that states it *"can only be carried out through an official dealer holding a partner contract with Kakao."*
- Real-time automatic receiving is out of scope here. Approaches exist that keep an app resident on an Android device intercepting notifications, but that is hard to recommend as a reproducible, maintainable business process. When the device turns off, orders vanish and nobody knows.
- So the input is the exported chat `.txt`. Someone exports the conversation once a day (or before the cut-off) and uploads it; everything after that is automatic.
- This looks like a compromise but the effect is large, because the target was never "checking KakaoTalk." It was "reading KakaoTalk while retyping into the order sheet." That second part is where the hours go.
The export format is not one format
The second trap. A KakaoTalk chat export .txt varies by platform and app version. A parser written against one format will read nothing at all from a file exported on a different device.
Format A (Android family)
2026년 8월 15일 오후 2:30, 김사장 : 배추 5박스 부탁드립니다
Format B (bracket family — date comes from the separator line)
--------------- 2026년 8월 15일 금요일 ---------------
[김사장] [오후 2:30] 양파 3망 주문이요
Format C (ISO date-time)
2026-08-15 14:30:00, 김사장 : 오이 2박스- The
Parse Chat Exportnode carries all three, trying them in order per line and keeping the first that matches. - AM/PM is converted to 24-hour time.
오후 2:30becomes14:30. Without this, morning and afternoon orders sort in the wrong order. - Multi-line orders are joined. When
감자도is followed by20kg 한 포대 같이요on the next line, they become one message. Real orders arrive exactly like this. - Lines matching no format are not discarded — they're counted as `unparsed_count`. A high number means your format differs and a pattern must be added. Silently processing zero orders is the worst outcome, so it surfaces as a number.
PATTERNS array if it differs. Kakao may change the format, so treat this list as a starting point, not a finished set.System lines have to be filtered first
Exports contain more than conversation — lines like 홍길동님이 들어왔습니다 (someone joined), 저장한 날짜 : … (export date), and 삭제된 메시지입니다 (deleted message). Left alone, they get appended to the order message directly above them and go to the model that way. The order text is corrupted, and it never registers as a parse failure.
So the parser checks each line for being a system line first, and drops it before the continuation branch. The order matters — filtering after joining is already too late.
What the model is asked to do, and what it isn't
The model structures only. Rules make the decisions. Five rules are stated in the prompt.
- Ignore anything that isn't an order — greetings, small talk, delivery questions.
- Mark
confidenceashighonly when item and quantity are both unambiguous. - When quantity is missing or the item is vague, mark it
lowand write the reason. - Never guess a quantity. If unknown, leave
quantityasnull. - When a correction appears ("make that 5 boxes, not 3"), keep only the final quantity.
temperature: 0, and anything ambiguous going to a human.The confidence gate
The model's answer isn't trusted as-is; it's filtered once more before registration. Only entries where `confidence` is `high`, the quantity is a real positive number, and the item name is non-empty register automatically. Everything else goes to the review queue — carrying the model's own stated reason.
Auto-register requires all three:
confidence === 'high'
quantity is a finite positive number
item is not an empty string
Any one failing → review queue, with the reason shown
Model response not valid JSON → everything to review (never silently dropped)What you need
- n8n — file uploads arrive from outside, so a publicly addressable URL is required.
- One Postgres database — a single orders table.
- An LLM API key — for order extraction. Cost is modest at typical chat volumes, but if you want to know the number before committing, pair this with our `ai-cost-guard` case.
- A Slack Bot User OAuth Token — for review-queue notifications.
- Several real chat exports from actual customers — needed to confirm the format and measure accuracy. Without them you cannot know whether the parser is right.
The orders table
CREATE TABLE IF NOT EXISTS kakao_orders (
id bigserial PRIMARY KEY,
sender text, -- chat display name; the start of customer mapping
item text NOT NULL,
quantity numeric,
unit text,
status text NOT NULL DEFAULT 'needs_review', -- confirmed|needs_review
source_file text, -- which export it came from
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS kakao_orders_status_idx
ON kakao_orders (status, created_at DESC);Keeping sender matters. A chat display name is not a customer code, so a separate display-name → customer-code mapping table is the natural next step. Start by storing names as-is and attach the mapping when you need it.
…님이 들어왔습니다, 저장한 날짜 : …) were being appended to the preceding order message, corrupting the model's input while never registering as a parse failure. The system-line check was moved ahead of the continuation branch and re-confirmed. The Kakao facts here (sending goes through official dealers, a business channel is required, informational messages only) were read directly from Kakao's official documentation on 2026-08-15. Not verified: the absence of a real-time receive API means we found no documented route, not that none exists. Actual LLM extraction accuracy and live Slack posting were also not verified. Last verified: 2026-08-15.Setup (35 minutes, plus format checking)
- Get a real export first — export one customer conversation and open it in a text editor. Work out which of the three formats it is. This is step one.
- Create the table — run the SQL above.
- Register the Postgres and Slack credentials — Test each, and
/invite @your-botin the channel. - Import the workflow — n8n → Workflows →
...→ Import from File. - Set model and API key — fill
REPLACE_WITH_YOUR_MODELinBuild Extraction PromptandREPLACE_WITH_YOUR_API_KEYinAI Extract Orders. - Test the parser on its own — run just
Parse Chat Exportand readmessage_countandunparsed_count. A high `unparsed_count` means your format differs; add a pattern to `PATTERNS`. Moving past this makes everything downstream meaningless. - Set the channel, attach credentials, activate.
- Reconcile by hand — count 10 orders in the exported conversation yourself and check that registered plus queued equals what you counted. Whether any order went missing is the key check.
- Decide how to run the review queue — watch for a week how many land there daily. Too many means strengthening the item examples in the prompt; almost none means sampling to confirm the gate isn't too loose.
What happens in edge cases
- Orders sent as photos — this workflow reads text only. Photos appear in the export as a marker and are filtered as system lines. Customers who order by photo need separate handling — the OCR approach in our
contract-intakecase is the starting point. - Several customers in one chat room — separated by
sender. Display names change, though, which breaks the mapping; a customer mapping table makes it resilient. - Correction messages — the prompt instructs keeping only the final quantity. Corrections spread over several days can still be missed, so consider a rule that routes the same item from the same customer arriving twice in quick succession to the review queue.
- Uploading the same file twice — this workflow has no duplicate defence, so the same orders register twice. In production, build an idempotency key from `source_file` plus the message timestamp and apply `ON CONFLICT` — our
payment-taxinvoiceandlead-capturecases show that pattern. - A model response that isn't JSON — everything goes to the review queue with a parse-failure flag. Nothing is silently dropped.
- A conversation containing no orders — nothing registers and nothing is sent. No notification on a day of pure small talk is correct.
We need a way to convert KakaoTalk conversations into an Excel file automatically.— An actual brief posted on a Korean freelance platform