We automate the generation of SEO descriptions for e-commerce stores where the catalog includes thousands of items. Manual writing of unique texts for each product would take months and millions — our system handles it in hours. But simply calling the neural network API is not enough: a well-thought-out architecture is needed, consisting of data preparation, prompt engineering, batch processing, validation, and a review interface. With over 10 years of experience in developing high-load systems, we guarantee stability and quality of generation.
Why This Works?
Modern language models — such as ChatGPT (GPT-4o) and Claude — generate meaningful texts if you give them structured data: name, category, attributes. According to the OpenAI documentation, contextual learning allows adapting the output to the task. Prompt engineering sets the tone, length, structure, and keyword requirements, so the result is relevant and SEO-optimized. Example for Nike Air Max 270 sneakers:
{
"id": "SKU-4821",
"name": "Nike Air Max 270 Sneakers",
"category": "Men's Shoes / Sneakers",
"brand": "Nike",
"attributes": { "material": "mesh + synthetic", "sole": "Air Max unit", "colors": ["black/white", "navy/grey"], "sizes": "40–46", "weight": "310g" },
"tags": ["running", "casual", "cushioning"],
"targetKeywords": ["buy nike air max 270", "nike air max 270 sneakers"]
}
How Is Uniqueness Guaranteed?
The model with temperature >0 always produces a different result. Additionally, we check each generation for duplicates via Elasticsearch — if the text matches an existing one, we trigger a new generation with a different seed. This eliminates duplicates in the catalog. This approach preserves uniqueness even during mass generation.
How Do We Design Prompts for Product Texts?
A prompt is not just "write a product description." A good prompt defines structure, tone, length, keyword requirements, and prohibitions. Example in TypeScript:
function buildProductSeoPrompt(product: Product, keywords: string[]): string {
return `
Write a product description for an e-commerce catalog in [language].
Product: ${product.name}
Category: ${product.category}
Brand: ${product.brand}
Key attributes: ${JSON.stringify(product.attributes)}
Tags: ${product.tags.join(", ")}
Requirements:
- Length: 200–400 words
- Include these keywords naturally: ${keywords.join(", ")}
- Structure: opening benefit statement → key features (3–5 points) → use cases → closing
- Tone: informative, no hype, no superlatives like "best" or "unique"
- Do NOT use: "this product", "we present to you", bullet points
- Do NOT start with the product name
- Write for a person who is comparing options
Output: plain text, no markdown, no headings.
`.trim();
}
Why Is Batch Processing with Queues Important?
Generating texts synchronously is not feasible — an LLM request takes 3–10 seconds, and there may be thousands of items. We use a task queue based on BullMQ with parallelism and retries.
import { Queue, Worker } from "bullmq";
import { openai } from "../lib/openai";
import { db } from "../lib/db";
const seoQueue = new Queue("seo-generation", {
connection: { host: "localhost", port: 6379 },
});
export async function queueProductsForGeneration(productIds: string[]) {
const jobs = productIds.map((id) => ({
name: "generate",
data: { productId: id },
opts: { attempts: 3, backoff: { type: "exponential", delay: 5000 }, removeOnComplete: 100 },
}));
await seoQueue.addBulk(jobs);
}
const worker = new Worker("seo-generation", async (job) => {
const product = await db.products.findById(job.data.productId);
if (!product) return;
const keywords = await getTargetKeywords(product);
const prompt = buildProductSeoPrompt(product, keywords);
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
temperature: 0.7,
max_tokens: 600,
});
const text = completion.choices[0].message.content?.trim();
if (!text) throw new Error("Empty response");
await db.productSeoTexts.upsert({
productId: product.id,
text,
status: "draft",
model: "gpt-4o-mini",
generatedAt: new Date(),
});
}, { connection: { host: "localhost", port: 6379 }, concurrency: 5 });
How Are Errors Handled During Generation?
If a request fails or returns an empty response, the task is automatically re-enqueued with exponential backoff. After three failed attempts, the product is marked as failed and sent for manual review. This ensures fault tolerance and minimizes data loss.
Quality Control Through Validation
The generated text is automatically checked: length, presence of all keywords, absence of stop phrases, keyword density. If validation fails, the product is flagged with needs_review and sent for regeneration with an adjusted prompt.
interface ValidationResult { passed: boolean; issues: string[] }
function validateSeoText(text: string, product: Product): ValidationResult {
const issues: string[] = [];
if (text.length < 500) issues.push(`Too short: ${text.length} chars`);
const missingKeywords = product.targetKeywords.filter(kw => !text.toLowerCase().includes(kw.toLowerCase()));
if (missingKeywords.length > 0) issues.push(`Missing keywords: ${missingKeywords.join(", ")}`);
const stopPhrases = ["this product", "we present to you", "unique", "best in its class"];
for (const phrase of stopPhrases) {
if (text.toLowerCase().includes(phrase)) issues.push(`Contains stop phrase: "${phrase}"`);
}
const wordCount = text.split(/\s+/).length;
for (const kw of product.targetKeywords) {
const kwCount = (text.toLowerCase().match(new RegExp(kw.toLowerCase(), "g")) || []).length;
if (kwCount / wordCount > 0.03) issues.push(`Keyword density too high for "${kw}": ${(kwCount / wordCount * 100).toFixed(1)}%`);
}
return { passed: issues.length === 0, issues };
}
Review Interface
Editors see a list of drafts with "Publish", "Regenerate", "Edit" buttons. Regeneration takes into account the reason for previous rejection — the worker adds it to the prompt. All drafts are stored in draft status; publication only occurs after confirmation.
Comparison: Manual vs AI Generation
| Parameter | Manual Copywriting | AI Generation |
|---|---|---|
| Speed per text | 15–30 minutes | 5–15 seconds |
| Cost for 10,000 texts | high (depends on authors) | significantly lower |
| Scalability | linear with number of authors | parallel queue |
| Quality control | depends on executor | automatic validation |
| Timeliness | manual updates | trigger on data change |
Example: for a large client in the clothing segment, we generated 12,000 descriptions in 4 hours. Prompts accounted for seasonality, gender, size chart. After initial generation, 8% of texts fell into needs_review due to high keyword density — after prompt adjustment, the percentage dropped to 2%. The final launch took one day.
Implementation Stages
| Stage | Description | Duration |
|---|---|---|
| Analysis | Collect data structure, test prompts on a sample | 1 week |
| Development | Integrate LLM, queue, validators, review interface | 2–3 weeks |
| Testing | Run on 500 products, iterative prompt refinement | 1 week |
| Launch | Generate entire catalog, train the team | 1 week |
What Is Included in the Implementation?
- Integration with LLM (OpenAI, Claude) via API
- Queue system on BullMQ with retries and backoff
- Prompt engineering for your specifics (categories, tone, keywords)
- Review and moderation interface (React + TypeScript)
- Validators: length, keywords, stop words, density
- Documentation and team training
- Support for one month after launch
Timeline and Cost
Implementation time — from 2 to 6 weeks depending on catalog size and integration complexity. Cost is calculated individually — the more products, the cheaper each individual text. Let's evaluate your project? Contact us — we'll tell you how to reduce the SEO content budget several times over.
Request a consultation — we will analyze your catalog and choose the optimal generation model. Get a prototype on your data and see the effect.







