Offer generator (Market integration)
Offer generator (Market integration)
Full API reference: https://api.mailoo.app/docs/v1
The Offer generator is a Market-only feature: wizards are scoped to a MARKET integration so they can resolve published products and price lists from the same catalog. Offer storage, SMTP, and outbound delivery are self-contained (separate tables and mail settings from other integration types).
For catalog read endpoints (types, products, price lists), continue to use ``market.external-read`` as documented in market-catalog-external-api{.interpreted-text role="doc"}.
Overview
- Dashboard: Project → Market integration → tab Offer generator --- list, create, edit, delete generators; configure wizard JSON, optional AI, and SMTP; external URL patterns for
pre-form. - Owner API (JWT / session Bearer): Create and update generators and mail settings under
/api/v1/projects/{projectUid}/integrations/{integrationId}/offer-generators…. - External API (``X-API-Key``): Same path prefix as Market external routes ---
/api/v1/market/{projectUid}/integrations/{integrationId}/offer-generators/…--- with separate scopes frommarket.external-read. - Scopes:
- ``offer-generator.external-read`` ---
GET …/offer-generators/{generatorId}(wizard JSON with catalog blocks resolved for published data only). - ``offer-generator.submit`` ---
POST …/offer-generators/{generatorId}/submissions(validate answers, optional AI, persist offer, send mail). - Persistence:
OfferGenerator,OfferGeneratorMailSettings,GeneratedOffer,OfferDelivery(Prisma). Nothing is written tomessagesor integrationoutboundMail.
Wizard configuration (version 1)
Stored as JSON on ``OfferGenerator.wizardConfig``. The last step must be a ``final`` block.
Step shape
id--- stable string id (referenced byaiblocksincludeStepIds).visibleWhen(optional) --- show the step whenvarequalsequalsor whenvaris inoneOf(exactly one of the two must be set).block--- discriminated bytype:- ``text`` --- Optional
bodyMarkdown;fields[]withname, optionallabel,kind(text|textarea|email|phone|boolean|pinGroup),required. ForpinGroup,options[](at least twovalue/labelpairs) is required; one value is stored in the fieldnamevariable. Each field may setuseMarkdownBody(boolean, default false): when true,markdownBody(markdown) is the field prompt instead oflabel; prepared wizards includemarkdownBodyHtmlper field whenmarkdownBodyis set. - ``choice`` ---
variable;options[]withvalue,label, optionalsetVariablesmap (client may merge into submitted variables). - ``product`` ---
productIds[];showFields; optionalshowPrice(default true). External GET embedsresolvedProducts. - ``priceList`` ---
priceListId; optionallineSkuFilter;showFields; optionalshowPrice. External GET embedsresolvedLines. - ``ai`` ---
promptTemplate(supports{{varName}}placeholders); optionaloutputVariable(defaultaiText); optionalincludeStepIdsfor extra JSON context. Requires AI settings on the generator (see below). - ``final`` ---
customerEmailVariable(must match a submitted variable name); optionalsubmitLabel,bodyMarkdown.
AI settings (optional)
Stored on ``OfferGenerator.aiSettings`` as openai-compatible JSON: baseUrl, model, and encrypted apiKeyEncrypted (same encryption mechanism as SMTP secrets). The API never returns the key; owner GET responses expose hasApiKey only.
Mail settings (required for successful email delivery)
PUT /api/v1/projects/{projectUid}/integrations/{integrationId}/offer-generators/{generatorId}/mail-settings --- host, port, secure, user, from, optional replyTo, optional notificationEmail (BCC-style internal alert), optional smtpPassword (write-only on PUT).
If mail settings are missing or SMTP send fails, the submission still creates a ``GeneratedOffer`` with status ``FAILED`` and diagnostics; ``OfferDelivery`` rows record per-channel attempts (``CUSTOMER`` / ``NOTIFICATION``).
Owner API (Bearer)
GET /api/v1/projects/{projectUid}/integrations/{integrationId}/offer-generatorsPOST /api/v1/projects/{projectUid}/integrations/{integrationId}/offer-generatorsGET /api/v1/projects/{projectUid}/integrations/{integrationId}/offer-generators/{generatorId}PUT /api/v1/projects/{projectUid}/integrations/{integrationId}/offer-generators/{generatorId}DELETE /api/v1/projects/{projectUid}/integrations/{integrationId}/offer-generators/{generatorId}GET /api/v1/projects/{projectUid}/integrations/{integrationId}/offer-generators/{generatorId}/mail-settings--- returns{ data: null }when no row exists yet; otherwise same non-secret fields as afterPUT.PUT /api/v1/projects/{projectUid}/integrations/{integrationId}/offer-generators/{generatorId}/mail-settings
POST create body includes key (slug per integration), optional name, status, wizardConfig, optional aiSettings (apiKey is write-only when present).
External API (X-API-Key)
Replace placeholders with your project UID, MARKET integration id, and generator id (CUID).
-
GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/offer-generators/{generatorId}Returns
{ data: { id, key, name, status, wizard } }wherewizard.steps[].blockmay includeresolvedProducts/resolvedLinesfor catalog blocks. -
POST {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/offer-generators/{generatorId}/submissionsBody:
{ "variables": { … } }--- all answers keyed by field / choice variable names. Response:{ data: { id, status, customerEmail } }withstatusCOMPLETEDorFAILED.
BFF / pre-form pattern
Keep two restricted keys on the server (never in the browser):
- Read key ---
offer-generator.external-read--- server loads the wizard once (or caches) and renders UI. - Submit key ---
offer-generator.submit--- server posts the finalvariablesJSON after validating on your side.
Catalog lookups from the client still go through your BFF using ``market.external-read`` if you need live product data outside the embedded resolvedProducts snapshot.
Related
market-catalog-external-api{.interpreted-text role="doc"} --- product and price list reads.market-catalog-csv{.interpreted-text role="doc"} --- bulk catalog edits in the dashboard.outbound-smtp{.interpreted-text role="doc"} --- not used for offer generator mail (offer generator uses its own SMTP table).