Email & MJML
Inklura's email stack has three moving parts: an MJML composer that produces
email-safe HTML, a Mautic bridge that actually delivers it, and two tRPC
routers on the company-manager API for campaign management
(campaigns) and reporting (emailAnalytics).
The MJML composer
The mail designers (maildesigner.inklura.fr / mail.inklura.fr) build
email-safe HTML with an MJML mirror kept alongside it for editing. An
AI builder chat streams its output over Server-Sent Events:
GET /api/builder/stream/<chatId>
The composer's job is producing the HTML; sending it is a separate step.
Delivery — the Mautic bridge
Email is not sent directly. Delivery is handed to a self-hosted Mautic
instance at infra.wd29.net via a bridge with two endpoints. The bridge secret
is server-side only — never call these from the browser.
Enqueue a message for delivery:
POST https://infra.wd29.net/api/mautic/queue/enqueue
Content-Type: application/json
{
"html": "<…rendered email HTML…>",
"subject": "…",
"tag": "…",
"slugs": ["…"],
"tenantId": "…",
"userId": "…",
"scheduledFor": "…",
"segmentId": "…"
}
| Field | Required | Notes |
|---|---|---|
html |
yes | The rendered, email-safe HTML. |
subject |
yes | Subject line. |
tag |
yes | Campaign/segment tag. |
slugs |
yes | Target slug list. |
tenantId |
no | Tenant scope. |
userId |
no | Actor. |
scheduledFor |
no | Deferred send time. |
segmentId |
no | Mautic segment. |
Check the status of an enqueued item:
POST https://infra.wd29.net/api/mautic/queue/status
Content-Type: application/json
{ "id": "<queue-item-id>" }
Campaign CRUD & send — the campaigns router
Campaign lifecycle lives in the campaigns tRPC router. Call it at
/api/trpc/campaigns.<procedure> using the standard
tRPC conventions:
| Procedure | Purpose |
|---|---|
getCampaigns |
List campaigns. |
getCampaignById |
Fetch one campaign. |
createCampaign |
Create a campaign. |
createCampaignWizard |
Create via the guided wizard flow. |
updateCampaign |
Update a campaign. |
deleteCampaign |
Delete a campaign. |
sendCampaign |
Send a campaign. |
pauseCampaign |
Pause a sending campaign. |
resumeCampaign |
Resume a paused campaign. |
duplicateCampaign |
Clone a campaign. |
getCampaignActivity |
Read campaign activity. |
getCampaignTags |
List campaign tags. |
createCampaignTag |
Create a campaign tag. |
Analytics — the emailAnalytics router
Reporting is a separate router, emailAnalytics. Every procedure is a
permissionProtectedProcedure(["analytics:read"]), so the caller needs a session
holding the analytics:read permission (see tRPC Procedures).
| Procedure | Returns |
|---|---|
getDashboardMetrics |
Headline dashboard metrics. |
getCampaignAnalytics |
Per-campaign analytics. |
getCampaignRevenue |
Revenue attributed to a campaign. |
getTopCampaigns |
Best-performing campaigns. |
getEngagementTrends |
Engagement over time. |
getProviderPerformance |
Delivery-provider performance. |
exportAnalytics |
Export analytics data. |
getUnsubscribeAnalytics |
Unsubscribe metrics. |
getUnsubscribeFunnel |
Unsubscribe funnel. |
getHealthStatus |
Analytics pipeline health. |
These procedures delegate to an external analytics HTTP service at
QUEUE_MANAGER_URL (default http://localhost:8085). When that service is
unreachable, the router degrades gracefully rather than failing the call — so
don't treat an empty or fallback response as a hard error.
Open & click tracking
The platform serves a tracking pixel and a click-redirect that proxy to the same analytics service:
${QUEUE_MANAGER_URL}/track/email/<id>/open
${QUEUE_MANAGER_URL}/track/email/<id>/click
An open is recorded when the pixel loads; a click is recorded as the redirect passes the reader through to the destination.
Related
- tRPC Procedures — how to call
campaigns.*andemailAnalytics.* - Marketing Journeys — the journey
EMAILstep, the one journey action that actually sends - Integrations Overview — the two-worlds map