1<!DOCTYPE html> 2<html lang="en"> 3<head> 4<meta charset="UTF-8"> 5<meta name="viewport" content="width=device-width, initial-scale=1.0"> 6<title>Marketing OS â Full-Funnel Organic Machine on Self-Hosted Stack | Case Study</title> 7<meta name="description" content="GAEL â a full-funnel marketing OS that generates, schedules, publishes, captures and nurtures leads across 6 channels. n8n + DeepSeek + Listmonk + Mautic + Chatwoot + Umami on one self-hosted VM. $0 ad spend, zero SaaS lock-in."> 8<link rel="preconnect" href="https://fonts.googleapis.com"> 9<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet"> 10<style> 11 :root { 12 --bg: #0c0c0c; 13 --bg2: #0c0c0c; 14 --bg3: #111111; 15 --cyan: #0EA5E9; 16 --cyan-l: #38BDF8; 17 --cyan-dim: rgba(14,165,233,0.1); 18 --purple: #A855F7; 19 --pur-dim: rgba(168,85,247,0.1); 20 --white: #E2E8F0; 21 --muted: #64748B; 22 --green: #10B981; 23 --grn-dim: rgba(16,185,129,0.1); 24 --yellow: #F59E0B; 25 --yel-dim: rgba(245,158,11,0.1); 26 --red: #EF4444; 27 --red-dim: rgba(239,68,68,0.1); 28 --border: rgba(255,255,255,0.08); 29 } 30 * { margin:0; padding:0; box-sizing:border-box; } 31 body { 32 background: var(--bg); 33 color: var(--white); 34 font-family: 'Inter', 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 35 font-size: 14px; 36 line-height: 1.7; 37 min-height: 100vh; 38 } 39 a { color: var(--cyan-l); text-decoration: none; } 40 a:hover { text-decoration: underline; color: var(--cyan); } 41 42 /* ââ HEADER ââ */ 43 .header { 44 background: linear-gradient(135deg, #0c0c0c 0%, #111111 60%, #0c0c0c 100%); 45 border-bottom: 1px solid var(--border); 46 padding: 56px 64px 48px; 47 position: relative; 48 overflow: hidden; 49 } 50 .header::after { 51 content: ''; 52 position: absolute; 53 top: -80px; right: -80px; 54 width: 500px; height: 500px; 55 background: radial-gradient(circle, rgba(14,165,233,0.08) 0%, transparent 65%); 56 pointer-events: none; 57 } 58 .header::before { 59 content: ''; 60 position: absolute; 61 bottom: -120px; left: -60px; 62 width: 400px; height: 400px; 63 background: radial-gradient(circle, rgba(168,85,247,0.04) 0%, transparent 65%); 64 pointer-events: none; 65 } 66 .header-inner { max-width: 1100px; margin: 0 auto; position: relative; z-index: 1; } 67 .header-tag { 68 display: inline-flex; align-items: center; gap: 6px; 69 padding: 4px 14px; border-radius: 20px; 70 background: var(--cyan-dim); border: 1px solid rgba(14,165,233,0.25); 71 font-size: 11px; font-weight: 600; color: var(--cyan); 72 letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 20px; 73 } 74 .header h1 { font-size: 38px; font-weight: 700; color: var(--white); margin-bottom: 10px; line-height: 1.2; } 75 .header h1 span { color: var(--cyan); } 76 .header-sub { font-size: 16px; color: var(--muted); margin-bottom: 32px; max-width: 700px; line-height: 1.6; } 77 .header-stats { display: flex; gap: 40px; flex-wrap: wrap; } 78 .stat { display: flex; flex-direction: column; gap: 2px; } 79 .stat-val { font-size: 24px; font-weight: 700; color: var(--cyan); font-family: 'DM Mono', monospace; } 80 .stat-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; } 81 82 /* ââ NAVIGATION ââ */ 83 .tab-bar { 84 display: flex; gap: 0; background: var(--bg2); 85 border-bottom: 1px solid var(--border); 86 padding: 0 64px; position: sticky; top: 0; z-index: 100; 87 } 88 .tab-btn { 89 padding: 14px 24px; font-size: 13px; font-weight: 600; color: var(--muted); 90 cursor: pointer; border: none; background: none; font-family: 'Inter', 'Outfit', sans-serif; 91 border-bottom: 2px solid transparent; transition: color 0.2s, border-color 0.2s; letter-spacing: 0.02em; 92 } 93 .tab-btn:hover { color: var(--cyan-l); } 94 .tab-btn.active { color: var(--cyan); border-bottom-color: var(--cyan); background: rgba(14,165,233,0.04); } 95 .tab-content { display: none; } 96 .tab-content.active { display: block; } 97 98 /* ââ CONTAINER ââ */ 99 .container { max-width: 1100px; margin: 0 auto; padding: 48px 64px; } 100 101 /* ââ SECTION TITLE ââ */ 102 .section-title { 103 font-size: 18px; font-weight: 700; color: var(--white); margin-bottom: 20px; 104 padding-bottom: 12px; border-bottom: 1px solid var(--border); 105 display: flex; align-items: center; gap: 10px; 106 } 107 .section-title::before { content: ''; width: 3px; height: 20px; background: var(--cyan); border-radius: 2px; } 108 109 /* ââ GRIDS ââ */ 110 .ref-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 48px; } 111 .ref-card { 112 background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; padding: 24px; 113 transition: border-color 0.2s; 114 } 115 .ref-card:hover { border-color: rgba(14,165,233,0.3); } 116 .ref-card.highlight { border-color: rgba(14,165,233,0.3); background: linear-gradient(135deg, rgba(14,165,233,0.06), rgba(168,85,247,0.03)); } 117 .ref-card.full { grid-column: 1 / -1; } 118 .ref-card h3 { font-size: 13px; font-weight: 700; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 14px; } 119 .ref-row { 120 display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; 121 padding: 6px 0; border-bottom: 1px solid rgba(14,165,233,0.05); font-size: 13px; 122 } 123 .ref-row:last-child { border-bottom: none; } 124 .ref-row .label { color: var(--muted); flex-shrink: 0; } 125 .ref-row .value { color: var(--white); font-family: 'DM Mono', monospace; font-size: 12px; text-align: right; } 126 127 /* ââ FLOW CARDS ââ */ 128 .flow-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; margin-bottom: 48px; } 129 .flow-card { 130 background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; 131 padding: 24px; position: relative; transition: border-color 0.2s; 132 } 133 .flow-card:hover { border-color: rgba(14,165,233,0.3); } 134 .flow-num { 135 position: absolute; top: -1px; left: 24px; background: var(--cyan); color: var(--bg); 136 font-size: 10px; font-weight: 700; padding: 2px 10px; border-radius: 0 0 6px 6px; 137 font-family: 'DM Mono', monospace; letter-spacing: 0.08em; 138 } 139 .flow-icon { 140 width: 42px; height: 42px; background: var(--cyan-dim); border: 1px solid rgba(14,165,233,0.2); 141 border-radius: 10px; display: flex; align-items: center; justify-content: center; 142 font-size: 18px; margin-top: 14px; margin-bottom: 14px; 143 } 144 .flow-card h4 { font-size: 15px; font-weight: 600; color: var(--white); margin-bottom: 8px; } 145 .flow-card p { font-size: 13px; color: var(--muted); line-height: 1.6; } 146 147 /* ââ INTENT TABLE ââ */ 148 .data-table { width: 100%; border-collapse: collapse; margin-bottom: 48px; } 149 .data-table thead th { 150 background: var(--bg3); font-size: 11px; font-weight: 700; color: var(--cyan); 151 text-transform: uppercase; letter-spacing: 0.08em; padding: 12px 16px; text-align: left; 152 border-bottom: 1px solid var(--border); 153 } 154 .data-table tbody td { 155 padding: 12px 16px; font-size: 13px; color: #94A3B8; border-bottom: 1px solid rgba(14,165,233,0.05); 156 } 157 .data-table tbody tr:hover { background: rgba(14,165,233,0.03); } 158 .data-table tbody td:first-child { color: var(--white); font-weight: 600; } 159 .data-table code { 160 font-family: 'DM Mono', monospace; font-size: 11px; background: var(--cyan-dim); 161 color: var(--cyan-l); padding: 2px 8px; border-radius: 4px; 162 } 163 164 /* ââ BADGE ââ */ 165 .badge { 166 display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 12px; 167 font-size: 11px; font-weight: 600; letter-spacing: 0.04em; 168 } 169 .badge-cyan { background: var(--cyan-dim); color: var(--cyan); border: 1px solid rgba(14,165,233,0.2); } 170 .badge-green { background: var(--grn-dim); color: var(--green); border: 1px solid rgba(16,185,129,0.2); } 171 .badge-purple { background: var(--pur-dim); color: var(--purple); border: 1px solid rgba(168,85,247,0.2); } 172 173 /* ââ CHALLENGE CARDS ââ */ 174 .challenge-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 48px; } 175 .challenge-card { 176 background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; padding: 28px; 177 transition: border-color 0.2s; 178 } 179 .challenge-card:hover { border-color: rgba(14,165,233,0.3); } 180 .challenge-card h4 { 181 font-size: 14px; font-weight: 600; color: var(--white); margin-bottom: 12px; 182 display: flex; align-items: center; gap: 8px; 183 } 184 .challenge-card h4 .emoji { font-size: 16px; } 185 .challenge-card p { font-size: 13px; color: var(--muted); line-height: 1.7; } 186 .challenge-card .solution { 187 margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(14,165,233,0.08); 188 font-size: 12px; color: #94A3B8; 189 } 190 .challenge-card .solution strong { color: var(--green); } 191 192 /* ââ ARCHITECTURE DIAGRAM ââ */ 193 .arch-diagram { 194 background: var(--bg3); border: 1px solid var(--border); border-radius: 12px; 195 padding: 32px; margin-bottom: 48px; overflow-x: auto; 196 } 197 .arch-diagram pre { 198 font-family: 'DM Mono', monospace; font-size: 13px; color: var(--cyan-l); 199 line-height: 1.8; white-space: pre; margin: 0; 200 } 201 .arch-diagram .comment { color: var(--muted); } 202 .arch-diagram .highlight { color: var(--yellow); } 203 .arch-diagram .accent { color: var(--purple); } 204 205 /* ââ RESULTS GRID ââ */ 206 .results-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 48px; } 207 .result-card { 208 background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; 209 padding: 24px; text-align: center; transition: border-color 0.2s; 210 } 211 .result-card:hover { border-color: rgba(14,165,233,0.3); } 212 .result-val { font-size: 28px; font-weight: 700; color: var(--cyan); font-family: 'DM Mono', monospace; margin-bottom: 4px; } 213 .result-label { font-size: 12px; color: var(--muted); line-height: 1.4; } 214 215 /* ââ DELIVERABLES LIST ââ */ 216 .deliverables { list-style: none; display: flex; flex-direction: column; gap: 12px; margin-bottom: 48px; } 217 .deliverables li { 218 display: flex; align-items: flex-start; gap: 12px; font-size: 14px; color: #94A3B8; 219 padding: 16px 20px; background: var(--bg2); border: 1px solid var(--border); 220 border-radius: 10px; transition: border-color 0.2s; 221 } 222 .deliverables li:hover { border-color: rgba(14,165,233,0.3); } 223 .deliverables li .icon { 224 width: 32px; height: 32px; background: var(--cyan-dim); border: 1px solid rgba(14,165,233,0.2); 225 border-radius: 8px; display: flex; align-items: center; justify-content: center; 226 font-size: 14px; flex-shrink: 0; 227 } 228 .deliverables li strong { color: var(--white); display: block; margin-bottom: 2px; } 229 230 /* ââ VERTICALS GRID ââ */ 231 .verticals-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 48px; } 232 .vertical-card { 233 background: var(--bg2); border: 1px solid var(--border); border-radius: 10px; 234 padding: 20px; text-align: center; transition: border-color 0.2s; 235 } 236 .vertical-card:hover { border-color: rgba(14,165,233,0.3); } 237 .vertical-card .v-icon { font-size: 28px; margin-bottom: 8px; } 238 .vertical-card .v-name { font-size: 13px; font-weight: 600; color: var(--white); } 239 .vertical-card .v-sub { font-size: 11px; color: var(--muted); margin-top: 2px; } 240 241 /* ââ TECH TAGS ââ */ 242 .tech-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 48px; } 243 .tech-tag { 244 padding: 4px 14px; border-radius: 20px; font-size: 12px; font-weight: 500; 245 background: var(--bg3); border: 1px solid var(--border); color: var(--muted); 246 transition: border-color 0.2s, color 0.2s; 247 } 248 .tech-tag:hover { border-color: rgba(14,165,233,0.3); color: var(--cyan-l); } 249 250 /* ââ CTA ââ */ 251 .cta-section { 252 background: linear-gradient(135deg, rgba(14,165,233,0.08) 0%, rgba(168,85,247,0.05) 100%); 253 border: 1px solid rgba(14,165,233,0.2); border-radius: 16px; 254 padding: 48px; text-align: center; margin-bottom: 48px; 255 } 256 .cta-section h2 { font-size: 24px; font-weight: 700; color: var(--white); margin-bottom: 12px; } 257 .cta-section p { font-size: 15px; color: var(--muted); margin-bottom: 28px; max-width: 500px; margin-left: auto; margin-right: auto; } 258 .cta-btn { 259 display: inline-flex; align-items: center; gap: 8px; 260 padding: 14px 32px; border-radius: 10px; 261 background: var(--cyan); color: var(--bg); font-size: 14px; font-weight: 600; 262 font-family: 'Inter', 'Outfit', sans-serif; border: none; cursor: pointer; 263 transition: background 0.2s; text-decoration: none; 264 } 265 .cta-btn:hover { background: var(--cyan-l); text-decoration: none; } 266 .cta-btn-outline { 267 display: inline-flex; align-items: center; gap: 8px; 268 padding: 14px 32px; border-radius: 10px; margin-left: 12px; 269 background: transparent; color: var(--cyan); font-size: 14px; font-weight: 600; 270 font-family: 'Inter', 'Outfit', sans-serif; border: 1px solid rgba(14,165,233,0.3); cursor: pointer; 271 transition: background 0.2s; text-decoration: none; 272 } 273 .cta-btn-outline:hover { background: rgba(14,165,233,0.1); text-decoration: none; } 274 275 /* ââ DEMO BANNER ââ */ 276 .demo-banner { 277 background: linear-gradient(135deg, rgba(168,85,247,0.1) 0%, rgba(14,165,233,0.08) 100%); 278 border: 1px solid rgba(168,85,247,0.2); border-radius: 12px; 279 padding: 24px 32px; margin-bottom: 48px; 280 display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; 281 } 282 .demo-banner-text h3 { font-size: 16px; font-weight: 600; color: var(--white); margin-bottom: 4px; } 283 .demo-banner-text p { font-size: 13px; color: var(--muted); } 284 .demo-banner-btn { 285 display: inline-flex; align-items: center; gap: 6px; 286 padding: 10px 24px; border-radius: 8px; 287 background: var(--purple); color: white; font-size: 13px; font-weight: 600; 288 font-family: 'Inter', 'Outfit', sans-serif; border: none; cursor: pointer; 289 text-decoration: none; transition: background 0.2s; flex-shrink: 0; 290 } 291 .demo-banner-btn:hover { background: #9333EA; text-decoration: none; } 292 293 /* ââ FOOTER ââ */ 294 .footer { 295 border-top: 1px solid var(--border); padding: 32px 64px; 296 text-align: center; color: var(--muted); font-size: 12px; 297 } 298 .footer a { color: var(--cyan); } 299 300 /* ââ NOTE BOX ââ */ 301 .note { 302 display: flex; align-items: flex-start; gap: 10px; padding: 14px 18px; 303 border-radius: 8px; font-size: 13px; margin-bottom: 24px; 304 } 305 .note-cyan { background: rgba(14,165,233,0.06); border: 1px solid rgba(14,165,233,0.15); color: #94A3B8; } 306 .note-green { background: rgba(16,185,129,0.06); border: 1px solid rgba(16,185,129,0.15); color: #94A3B8; } 307 308 /* ââ RESPONSIVE ââ */ 309 @media (max-width: 768px) { 310 .header { padding: 32px 24px 28px; } 311 .header h1 { font-size: 26px; } 312 .header-stats { gap: 24px; } 313 .tab-bar { padding: 0 16px; overflow-x: auto; } 314 .tab-btn { padding: 12px 16px; font-size: 12px; white-space: nowrap; } 315 .container { padding: 32px 24px; } 316 .ref-grid, .flow-grid, .challenge-grid { grid-template-columns: 1fr; } 317 .results-grid, .verticals-grid { grid-template-columns: 1fr 1fr; } 318 .ref-card.full { grid-column: auto; } 319 .flow-card { grid-column: auto; } 320 .cta-btn-outline { margin-left: 0; margin-top: 12px; } 321 .demo-banner { flex-direction: column; text-align: center; } 322 .footer { padding: 24px; } 323 } 324 325 /* liquid-glass card treatment */ 326 .liquid-glass { 327 background: rgba(255,255,255,0.01); 328 background-blend-mode: luminosity; 329 backdrop-filter: blur(4px); 330 border: none; 331 box-shadow: inset 0 1px 1px rgba(255,255,255,0.1); 332 position: relative; overflow: hidden; 333 } 334 .liquid-glass::before { 335 content: ''; position: absolute; inset: 0; border-radius: inherit; 336 padding: 1.4px; 337 background: linear-gradient(180deg, 338 rgba(255,255,255,0.45) 0%, rgba(255,2
33855,255,0.15) 20%, 339 rgba(255,255,255,0) 40%, rgba(255,255,255,0) 60%, 340 rgba(255,255,255,0.15) 80%, rgba(255,255,255,0.45) 100%); 341 -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); 342 -webkit-mask-composite: xor; mask-composite: exclude; 343 pointer-events: none; 344 } 345</style> 346</head> 347<body> 348 349<!-- Background video --> 350<div style="position:fixed;inset:0;z-index:0;pointer-events:none"> 351 <video autoplay loop muted playsinline style="width:100%;height:100%;object-fit:cover;opacity:0.65" src="/bg-video.mp4"></video> 352 <div style="position:absolute;inset:0;background:linear-gradient(to bottom,rgba(12,12,12,0.7),rgba(12,12,12,0.4),rgba(12,12,12,0.8))"></div> 353</div> 354<!-- Ambient orbs --> 355<div style="position:fixed;inset:0;pointer-events:none;z-index:0"> 356 <div style="position:absolute;top:0;left:25%;width:600px;height:600px;background:rgba(14,165,233,0.05);border-radius:50%;filter:blur(120px)"></div> 357 <div style="position:absolute;top:33%;right:25%;width:500px;height:500px;background:rgba(168,85,247,0.04);border-radius:50%;filter:blur(120px)"></div> 358</div> 359<div class="page-content" style="position:relative;z-index:10"> 360 361<!-- HEADER --> 362<div class="header"> 363 <div class="header-inner"> 364 <div class="header-tag">◆ Case Study · Marketing OS</div> 365 <h1>One Brief In, Full-Funnel Campaign Out — Across 6 Channels, 3 Languages, <span style="color:var(--cyan)">Zero Ad Spend</span></h1> 366 <p class="header-desc">GAEL is a self-hosted marketing operating system. Feed it a campaign brief and it generates, adapts, schedules, publishes, captures leads, and nurtures them — with n8n orchestrating DeepSeek, Listmonk, Mautic, Chatwoot and Umami on a single VM. No Zapier, no Make, no Mailchimp, no HubSpot.</p> 367 <div class="header-stats"><div class="header-stats"> 368 <div class="stat"><div class="stat-val">6</div><div class="stat-label">Channels Orchestrated</div></div> 369 <div class="stat"><div class="stat-val">20+</div><div class="stat-label">n8n Workflows</div></div> 370 <div class="stat"><div class="stat-val">3</div><div class="stat-label">Languages</div></div> 371 <div class="stat"><div class="stat-val">Unlimited</div><div class="stat-label">Email Capacity</div></div> 372 <div class="stat"><div class="stat-val">$0</div><div class="stat-label">Ad Spend</div></div> 373 </div> 374 </div> 375</div> 376 377<!-- TABS --> 378<div class="tab-bar"> 379 <button class="tab-btn active" onclick="switchTab('overview')">Overview</button> 380 <button class="tab-btn" onclick="switchTab('architecture')">Architecture</button> 381 <button class="tab-btn" onclick="switchTab('challenges')">Challenges</button> 382 <button class="tab-btn" onclick="switchTab('results')">Results</button> 383 <button class="tab-btn" onclick="switchTab('contact')">Work With Me</button> 384</div> 385 386<!-- OVERVIEW --> 387<div id="tab-overview" class="tab-content active"> 388 <div class="container"> 389 390 <div class="demo-banner"> 391 <div class="demo-banner-label">Production deployment</div> 392 <div class="demo-banner-title">Running in production for a multilingual content brand</div> 393 <div class="demo-banner-link" style="cursor:default;">Client engagement · NDA</div> 394 </div> 395 396 <div class="section-title">The 4 Campaign Templates</div> 397 <table class="data-table"> 398 <thead> 399 <tr><th>Template</th><th>Duration</th><th>Cadence</th><th>Use Case</th></tr> 400 </thead> 401 <tbody> 402 <tr> 403 <td><strong>Product Launch</strong></td> 404 <td>14 days</td> 405 <td>Teaser → Reveal → Social proof → Urgency</td> 406 <td>New product, feature release, course drop</td> 407 </tr> 408 <tr> 409 <td><strong>Evergreen</strong></td> 410 <td>Continuous</td> 411 <td>3 pins/day, 1 post/day, 2 tweets/day, weekly newsletter</td> 412 <td>Always-on traffic to landing pages</td> 413 </tr> 414 <tr> 415 <td><strong>Flash Sale</strong></td> 416 <td>24–72 h</td> 417 <td>All channels, posts every 2–4 h, email blast + reminder</td> 418 <td>Promo, last-call, inventory clearance</td> 419 </tr> 420 <tr> 421 <td><strong>Lead Magnet</strong></td> 422 <td>Continuous</td> 423 <td>Free value → landing → auto-delivery → nurture</td> 424 <td>Ebook, checklist, webinar opt-in</td> 425 </tr> 426 </tbody> 427 </table> 428 429 <div class="note note-cyan"> 430 <div>
430New campaign types can be added as n8n sub-workflows in hours. The brief schema, content engine and distribution hub stay the same — only the cadence template changes.</div> 431 </div> 432 433 <div class="section-title">Tech Stack</div> 434 <div class="ref-grid"> 435 <div class="ref-card"> 436 <h3>Orchestration</h3> 437 <div class="ref-row"><span class="label">n8n</span><span class="value">20+ workflows</span></div> 438 <div class="ref-row"><span class="label">PostgreSQL</span><span class="value">content queue + CRM</span></div> 439 <div class="ref-row"><span class="label">Redis</span><span class="value">rate limits + cache</span></div> 440 <div class="ref-row"><span class="label">Telegram bot</span><span class="value">ops + alerts</span></div> 441 </div> 442 <div class="ref-card"> 443 <h3>Content + AI</h3> 444 <div class="ref-row"><span class="label">DeepSeek</span><span class="value">copy + chat AI</span></div> 445 <div class="ref-row"><span class="label">GPT Image</span><span class="value">visuals</span></div> 446 <div class="ref-row"><span class="label">Leon (Remotion)</span><span class="value">video render API</span></div> 447 <div class="ref-row"><span class="label">Google TTS</span><span class="value">voiceover</span></div> 448 </div> 449 <div class="ref-card"> 450 <h3>Capture + Nurture</h3> 451 <div class="ref-row"><span class="label">Mautic</span><span class="value">landing pages + forms</span></div> 452 <div class="ref-row"><span class="label">Listmonk</span><span class="value">email (36 lists)</span></div> 453 <div class="ref-row"><span class="label">Chatwoot</span><span class="value">omnichannel inbox</span></div> 454 <div class="ref-row"><span class="label">Umami</span><span class="value">analytics + funnels</span></div> 455 </div> 456 </div> 457 458 </div> 459</div> 460 461<div id="tab-architecture" class="tab-content"> 462 <div class="container"> 463 464 <div class="section-title">System Architecture</div> 465 <p>GAEL is structured as a 6-layer pipeline. Every layer is self-hosted on a single Oracle Cloud ARM VM, orchestrated by n8n workflows, and state lives in PostgreSQL. No external SaaS dependencies for the core pipeline — only the channel APIs themselves.</p> 466 467 <div class="arch-diagram" style="padding:32px; overflow-x:auto;"> 468 <div style="display:flex; flex-direction:column; gap:14px; min-width:760px;"> 469 470 <div style="background:linear-gradient(135deg, var(--cyan-dim), rgba(14,165,233,0.03)); border:1px solid var(--cyan); border-radius:12px; padding:16px 22px;"> 471 <div style="font-size:11px; color:var(--cyan); font-family:'DM Mono',monospace; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:8px;">Layer 1 · Input</div> 472 <div style="font-size:14px; color:var(--white);">Campaign brief webhook · scheduled triggers · Telegram commands</div> 473 </div> 474 475 <div style="background:linear-gradient(135deg, rgba(168,85,247,0.1), rgba(168,85,247,0.02)); border:1px solid var(--purple); border-radius:12px; padding:16px 22px;"> 476 <div style="font-size:11px; color:var(--purple); font-family:'DM Mono',monospace; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:8px;">Layer 2 · Content Engine (n8n)</div> 477 <div style="font-size:14px; color:var(--white);">Campaign Router → DeepSeek text gen · GPT Image visuals · Leon video render · Mautic page gen</div> 478 </div> 479 480 <div style="background:linear-gradient(135deg, rgba(168,85,247,0.1), rgba(168,85,247,0.02)); border:1px solid var(--purple); border-radius:12px; padding:16px 22px;"> 481 <div style="font-size:11px; color:var(--purple); font-family:'DM Mono',monospace; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:8px;">Layer 3 · Distribution Hub</div> 482 <div style="font-size:14px; color:var(--white);">Postgres content_queue → hourly publisher → Pinterest · Facebook · Twitter/X · Instagram · TikTok · Listmonk</div> 483 </div> 484 485 <div style="background:linear-gradient(135deg, var(--grn-dim), rgba(16,185,129,0.02)); border:1px solid var(--green); border-radius:12px; padding:16px 22px;"> 486 <div style="font-size:11px; color:var(--green); font-family:'DM Mono',monospace; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:8px;">Layer 4 · Capture (Mautic)</div> 487 <div style="font-size:14px; color:var(--white);">Landing pages per campaign · forms with UTM tracking ·
487 lead scoring by engagement</div> 488 </div> 489 490 <div style="background:linear-gradient(135deg, var(--grn-dim), rgba(16,185,129,0.02)); border:1px solid var(--green); border-radius:12px; padding:16px 22px;"> 491 <div style="font-size:11px; color:var(--green); font-family:'DM Mono',monospace; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:8px;">Layer 5 · Nurture</div> 492 <div style="font-size:14px; color:var(--white);">Listmonk drip campaigns (36 segmented lists) · Chatwoot omnichannel inbox with AI agent</div> 493 </div> 494 495 <div style="background:linear-gradient(135deg, var(--yel-dim), rgba(245,158,11,0.02)); border:1px solid var(--yellow); border-radius:12px; padding:16px 22px;"> 496 <div style="font-size:11px; color:var(--yellow); font-family:'DM Mono',monospace; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:8px;">Layer 6 · Intelligence</div> 497 <div style="font-size:14px; color:var(--white);">Umami funnels · engagement scoring · A/B test feedback loop → back into Layer 1</div> 498 </div> 499 500 </div> 501 </div> 502 503 <div class="section-title">Core n8n Workflows</div> 504 <table class="data-table"> 505 <thead> 506 <tr><th>Workflow</th><th>Trigger</th><th>What it does</th></tr> 507 </thead> 508 <tbody> 509 <tr><td><code>campaign-launcher</code></td><td>Webhook / manual</td><td>Receives brief, decides channels, kicks off content generation, starts email sequence</td></tr> 510 <tr><td><code>content-generator</code></td><td>Called by launcher</td><td>DeepSeek prompt chain adapts copy per channel (length, tone, hashtags)</td></tr> 511 <tr><td><code>multi-publisher</code></td><td>Cron hourly</td><td>Pulls from content_queue, publishes to the right API, marks published</td></tr> 512 <tr><td><code>lead-processor</code></td><td>Mautic webhook</td><td>UTM attribution, Listmonk list assignment, nurture kickoff, Chatwoot contact creation</td></tr> 513 <tr><td><code>chatwoot-ai-agent</code></td><td>Chatwoot webhook</td><td>Detects language, loads context, DeepSeek reply, escalates hot leads to Telegram</td></tr> 514 <tr><td><code>analytics-loop</code></td><td>Cron nightly</td><td>Umami event rollup, DeepSeek scoring, drafts new content ideas, notifies via Telegram</td></tr> 515 </tbody> 516 </table> 517 518 <div class="section-title">Content Queue Schema</div> 519 <table class="data-table"> 520 <thead> 521 <tr><th>Column</th><th>Type</th><th>Purpose</th></tr> 522 </thead> 523 <tbody> 524 <tr><td><code>campaign_id</code></td><td>VARCHAR</td><td>Links every post back to the originating brief</td></tr> 525 <tr><td><code>platform</code></td><td>VARCHAR</td><td>pinterest / facebook / twitter / instagram / tiktok / email</td></tr> 526 <tr><td><code>content</code></td><td>JSONB</td><td>Platform-specific payload (copy, hashtags, media URLs)</td></tr> 527 <tr><td><code>scheduled_at</code></td><td>TIMESTAMP</td><td>When the publisher should fire</td></tr> 528 <tr><td><code>status</code></td><td>VARCHAR</td><td>pending / published / failed — drives retry logic</td></tr> 529 <tr><td><code>response</code></td><td>JSONB</td><td>Raw API response for audit + debugging</td></tr> 530 </tbody> 531 </table> 532 533 <div class="note note-cyan"> 534 <div>Adding a new channel is a matter of implementing one n8n HTTP node against the platform API and inserting rows into <code>content_queue</code> with the new <code>platform</code> value. The publisher does the rest.</div> 535 </div> 536 537 </div> 538</div> 539 540<div id="tab-challenges" class="tab-content"> 541 <div class="container"> 542 543 <div class="section-title">Engineering Challenges Solved</div> 544 <div class="challenge-grid"> 545 <div class="challenge-card"> 546 <h4><span class="emoji">📌</span> Pinterest Board ID Precision Loss</h4> 547 <p>Pinterest Board IDs exceed <code>Number.MAX_SAFE_INTEGER</code> (>10<sup>18</sup>). Any dynamic JS computation silently produces wrong IDs and posts land on the wrong board.</p> 548 <div class="solution"><strong>Solution:</strong> Static string mapping in the publisher workflow instead of dynamic computation. Discovered after hours debugging "wrong board" errors — the kind of edge case you only catch in production.</div> 549 </div> 550 <div class="challenge-card"> 551 <h4><span class="emoji">🌐</span> Native Multilingual Copy (Not Translation)</h4> 552 <p>Machine-translated marketing copy sounds robotic and kills engagement. But generating content 3× multiplies LLM cost and drift.</p> 553 <div class="solution"><strong>Solution:</strong> Single DeepSeek prompt with language-specific "voice" variables. Scripts are written natively in ES/EN/PT from the same brief, not translated. Cost stays flat, quality stays high.</div> 554 </div> 555 <div class="challenge-card"> 556 <h4><span class="emoji">⏱️</span> Rate Limits Across 6 Channels</h4> 557 <p>Every platform has different rate limits: Twitter 1500/mo, Listmonk 300/day, Pinterest polite backoff, Facebook burst limits.</p> 558 <div class="solution"><strong>Solution:</strong> Centralized content_queue with per-platform scheduling windows. Redis tracks burst counters. Publisher respects platform caps and defers overflow to the next window automatically.</div> 559 </div> 560 <div class="challenge-card"> 561 <h4><span class="emoji">🔑</span> Instagram Meta Business Lockout</h4> 562 <p>Meta restricted API access mid-project, killing direct publishing. A hard blocker for most stacks.<
562/p> 563 <div class="solution"><strong>Solution:</strong> Pivoted Instagram to Chatwoot as the inbox layer, which preserves DM conversations and the AI chat agent. Publishing stays manual for IG, but capture + nurture kept working — loss contained.</div> 564 </div> 565 <div class="challenge-card"> 566 <h4><span class="emoji">📦</span> State Consistency Across 20+ Workflows</h4> 567 <p>n8n workflows run independently. A lead can trigger lead-processor before chatwoot-ai-agent finishes writing the contact.</p> 568 <div class="solution"><strong>Solution:</strong> PostgreSQL is the single source of truth. Every workflow reads/writes through explicit upserts with conflict resolution. No in-workflow state, no race conditions.</div> 569 </div> 570 <div class="challenge-card"> 571 <h4><span class="emoji">🔊</span> AI Chat Without Hallucination</h4> 572 <p>Letting a raw LLM answer lead DMs is a legal and brand risk. But scripted bots feel dead.</p> 573 <div class="solution"><strong>Solution:</strong> DeepSeek gets strict system prompt + contact context from Postgres + product catalog as retrieval. Hot leads detected by intent scoring are always routed to a human via Telegram before any commitment.</div> 574 </div> 575 </div> 576 577 <div class="note note-cyan"> 578 💡 <span>This entire marketing OS pattern ports to any vertical. Change the brief schema, swap the language mix, point to different channel APIs — the orchestration layer stays the same.</span> 579 </div> 580 581 </div> 582</div> 583 584<div id="tab-results" class="tab-content"> 585 <div class="container"> 586 587 <div class="section-title">Results & Metrics</div> 588 <div class="results-grid"> 589 <div class="result-card"> 590 <div class="result-val">6</div> 591 <div class="result-label">Channels<br>orchestrated</div> 592 </div> 593 <div class="result-card"> 594 <div class="result-val">20+</div> 595 <div class="result-label">n8n workflows<br>in production</div> 596 </div> 597 <div class="result-card"> 598 <div class="result-val"></div> 599 <div class="result-label">Pins ready<br>+ dynamic gen</div> 600 </div> 601 <div class="result-card"> 602 <div class="result-val">$0</div> 603 <div class="result-label">Ad spend<br>CAC organic</div> 604 </div> 605 </div> 606 607 <div class="section-title">Automation Coverage</div> 608 <div class="ref-grid"> 609 <div class="ref-card"> 610 <h3>Publishing Cadence</h3> 611 <div class="ref-row"><span class="label">Pinterest</span><span class="value">3 pins/day/lang</span></div> 612 <div class="ref-row"><span class="label">Facebook</span><span class="value">1-3 posts/day</span></div> 613 <div class="ref-row"><span class="label">Twitter/X</span><span class="value">2-5 tweets/day (1500/mo cap)</span></div> 614 <div class="ref-row"><span class="label">Email (Listmonk)</span><span class="value">multi-list drip campaigns</span></div> 615 <div class="ref-row"><span class="label">Video (via Leon)</span><span class="value">3/day multi-platform</span></div> 616 </div> 617 <div class="ref-card"> 618 <h3>SaaS Replaced</h3> 619 <div class="ref-row"><span class="label">Zapier / Make</span><span class="value">→ n8n</span></div> 620 <div class="ref-row"><span class="label">Mailchimp / SendGrid</span><span class="value">→ Listmonk</span></div> 621 <div class="ref-row"><span class="label">HubSpot / ActiveCampaign</span><span class="value">→ Mautic</span></div> 622 <div class="ref-row"><span class="label">Intercom / Drift</span><span class="value">→ Chatwoot</span></div> 623 <div class="ref-row"><span class="label">Google Analytics</span><span class="value">→ Umami</span></div> 624 <div class="ref-row"><span class="label">Monthly SaaS saved</span><span class="value">~$400+/mo</span></div> 625 </div> 626 </div> 627 628 <div class="section-title">Deliverables</div> 629 <ul class="deliverables"> 630 <li> 631 <div class="icon">🤖</div> 632 <div><strong>20+ n8n Workflows</strong>Campaign launcher, content generator, multi-publisher, lead processor, chat AI agent, intelligence monitor</div> 633 </li> 634 <li> 635 <div class="icon">📦</div> 636 <div><strong>Content Queue Schema</strong>PostgreSQL source of truth for every scheduled post, across every platform, with full audit trail</div> 637 </li> 638 <li> 639 <div class="icon">📧</div> 640 <div><strong>Trilingual Email System</strong>Listmonk (36 lists) + Mautic (9 campaigns, 15 emails, lead scoring) — all natively in ES/EN/PT</div> 641 </li> 642 <li> 643 <div class="icon">💬</div> 644 <div><strong>Omnichannel Chat Agent</strong>Chatwoot inbox with DeepSeek AI agent, language auto-detect, hot-lead escalation to Telegram</div> 645 </li> 646 <li> 647 <div class="icon">📈</div> 648 <div><strong>Intelligence + Analytics</strong>Umami funnels + engagement scoring + A/B test feedback loop back into campaign planning</
648div> 649 </li> 650 <li> 651 <div class="icon">📚</div> 652 <div><strong>Operations Runbook</strong>Telegram command interface (<code>/campaign</code>, <code>/stats</code>, <code>/queue</code>, <code>/lead</code>) and full docs for handover</div> 653 </li> 654 </ul> 655 656 <div class="section-title">Technology Tags</div> 657 <div class="tech-tags"> 658 <span class="tech-tag">n8n</span> 659 <span class="tech-tag">PostgreSQL</span> 660 <span class="tech-tag">Redis</span> 661 <span class="tech-tag">DeepSeek</span> 662 <span class="tech-tag">GPT Image</span> 663 <span class="tech-tag">Listmonk</span> 664 <span class="tech-tag">Mautic</span> 665 <span class="tech-tag">Chatwoot</span> 666 <span class="tech-tag">Umami</span> 667 668 <span class="tech-tag">Pinterest API</span> 669 <span class="tech-tag">Facebook Graph API</span> 670 <span class="tech-tag">Twitter API</span> 671 <span class="tech-tag">TikTok API</span> 672 <span class="tech-tag">Telegram Bot</span>
673 <span class="tech-tag">Docker</span> 674 <span class="tech-tag">Oracle Cloud</span> 675 <span class="tech-tag">Multilingual</span> 676 <span class="tech-tag">Lead Scoring</span> 677 <span class="tech-tag">Self-Hosted</span> 678 </div> 679 680 </div> 681</div> 682 683<div id="tab-contact" class="tab-content"> 684 <div class="container"> 685 686 <div class="cta-section"> 687 <h2>Need a Marketing OS That <span style="color:var(--cyan)">Runs Itself</span>?</h2> 688 <p>I build self-hosted, full-funnel marketing systems — from campaign brief to publish to capture to nurture. One VM, one codebase, zero SaaS lock-in.</p> 689 <a href="https://contra.com/leongael" class="cta-btn" target="_blank">💬 Hire Me on Contra</a> 690 <a href="/" class="cta-btn-outline">← Back to portfolio</a> 691 </div> 692 693 <div class="section-title">What I Build</div> 694 <div class="ref-grid"> 695 <div class="ref-card"> 696 <h3>Voice AI Agents</h3> 697 <div class="ref-row"><span class="label">Inbound receptionists</span><span class="value">Any language</span></div> 698 <div class="ref-row"><span class="label">Outbound sales agents</span><span class="value">Cold + warm</span></div> 699 <div class="ref-row"><span class="label">Appointment booking</span><span class="value">Real-time CRM</span></div> 700 <div class="ref-row"><span class="label">Emergency routing</span><span class="value">Live transfer</span></div> 701 </div> 702 <div class="ref-card"> 703 <h3>Content Automation</h3> 704 <div class="ref-row"><span class="label">AI content generation</span><span class="value">Multilingual</span></div> 705 <div class="ref-row"><span class="label">Blog & social publishing</span><span class="value">Multi-platform</span></div> 706 <div class="ref-row"><span class="label">Email drip campaigns</span><span class="value">Full funnel</span></div> 707 <div class="ref-row"><span class="label">Trend intelligence</span><span class="value">Automated</span></div> 708 </div> 709 <div class="ref-card"> 710 <h3>Video Production</h3> 711 <div class="ref-row"><span class="label">AI script writing</span><span class="value">Native multilingual</span></div> 712 <div class="ref-row"><span class="label">AI image generation</span><span class="value">GPT Image</span></div> 713 <div class="ref-row"><span class="label">TTS voiceover</span><span class="value">WaveNet voices</span></div> 714 <div class="ref-row"><span class="label">Remotion render</span><span class="value">Custom templates</span></div> 715 </div> 716 <div class="ref-card"> 717 <h3>
717Infrastructure</h3> 718 <div class="ref-row"><span class="label">N8N workflows</span><span class="value">Self-hosted</span></div> 719 <div class="ref-row"><span class="label">PostgreSQL + Redis</span><span class="value">Production-grade</span></div> 720 <div class="ref-row"><span class="label">Monitoring & alerts</span><span class="value">24/7</span></div> 721 <div class="ref-row"><span class="label">Documentation</span><span class="value">Runbooks + guides</span></div> 722 </div> 723 </div> 724 725 <div class="note note-green"> 726 ✅ <span>Based in Honduras (UTC-6). Available for async collaboration across all timezones. Fluent in English, Spanish, and French.</span> 727 </div> 728 729 </div> 730</div> 731 732<!-- FOOTER --> 733<div class="footer"> 734 <p>© 2026 · Freelance engineering portfolio</p> 735</div> 736 737<!-- SCRIPTS -->
738<script> 739function switchTab(name) { 740 document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); 741 document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active')); 742 document.getElementById('tab-' + name).classList.add('active'); 743 event.target.classList.add('active'); 744 window.scrollTo({ top: 0, behavior: 'smooth' }); 745} 746</script>
746 747 748</div><!-- /page-content --> 749 750</body> 751</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.