# Mautic Abandoned Cart Recovery Setup ## Overview Use your existing Mautic instance for abandoned cart recovery instead of paying for Klaviyo. **Mautic URL:** https://mautic.nodecrew.me ## How It Works ``` 1. User adds item to cart ↓ 2. Storefront sends event to Mautic (via API or tracking pixel) ↓ 3. Mautic creates/updates contact with cart data ↓ 4. Campaign waits 1 hour ↓ 5. If no purchase → Send abandoned cart email ↓ 6. User clicks email → Cart restored → Convert! ``` ## Step 1: Set Up Mautic Tracking ### Option A: Mautic Tracking Pixel (JavaScript) Add to your Next.js storefront: ```typescript // lib/mautic.ts export function trackAddToCart(product: any, quantity: number) { if (typeof window !== 'undefined' && (window as any).mt) { (window as any).mt('send', 'pageview', { page_title: `Added to Cart: ${product.name}`, page_url: window.location.href, product_name: product.name, product_sku: product.variants[0]?.sku, product_price: product.variants[0]?.channelListings[0]?.price?.amount, quantity: quantity, event: 'add_to_cart' }); } } export function trackCheckoutStarted(checkout: any) { if (typeof window !== 'undefined' && (window as any).mt) { (window as any).mt('send', 'pageview', { page_title: 'Checkout Started', page_url: window.location.href, checkout_value: checkout.totalPrice?.amount, checkout_id: checkout.id, event: 'checkout_started' }); } } export function trackOrderCompleted(order: any) { if (typeof window !== 'undefined' && (window as any).mt) { (window as any).mt('send', 'pageview', { page_title: 'Order Completed', page_url: window.location.href, order_total: order.total.gross.amount, order_id: order.id, event: 'purchase_completed' }); } } ``` ```typescript // pages/_app.tsx or layout.tsx import Script from 'next/script'; export default function RootLayout({ children }) { return (
{/* Mautic Tracking */} {children} ); } ``` ### Option B: Direct Mautic API Integration More reliable for e-commerce events: ```typescript // lib/mautic-api.ts const MAUTIC_URL = 'https://mautic.nodecrew.me'; const MAUTIC_USERNAME = process.env.MAUTIC_API_USER; const MAUTIC_PASSWORD = process.env.MAUTIC_API_PASS; export async function createOrUpdateContact(email: string, data: any) { const response = await fetch(`${MAUTIC_URL}/api/contacts/new`, { method: 'POST', headers: { 'Authorization': `Basic ${Buffer.from(`${MAUTIC_USERNAME}:${MAUTIC_PASSWORD}`).toString('base64')}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ email: email, firstname: data.firstName, lastname: data.lastName, phone: data.phone, // Custom fields for cart cart_items: JSON.stringify(data.cartItems), cart_value: data.cartValue, cart_abandoned: true, cart_abandoned_at: new Date().toISOString(), last_product_added: data.lastProductName, }), }); return response.json(); } export async function trackCartAbandoned(email: string, checkout: any) { return createOrUpdateContact(email, { cartItems: checkout.lines.map((line: any) => ({ name: line.variant.name, quantity: line.quantity, price: line.totalPrice.gross.amount, })), cartValue: checkout.totalPrice.gross.amount, lastProductName: checkout.lines[0]?.variant.name, }); } export async function markCartRecovered(email: string) { const response = await fetch(`${MAUTIC_URL}/api/contacts/edit`, { method: 'PATCH', headers: { 'Authorization': `Basic ${Buffer.from(`${MAUTIC_USERNAME}:${MAUTIC_PASSWORD}`).toString('base64')}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ email: email, cart_abandoned: false, cart_recovered: true, cart_recovered_at: new Date().toISOString(), }), }); return response.json(); } ``` ## Step 2: Create Custom Fields in Mautic 1. Go to https://mautic.nodecrew.me 2. Settings → Custom Fields 3. Create these fields: | Field Label | Alias | Data Type | Default Value | |-------------|-------|-----------|---------------| | Cart Items | cart_items | Text | | | Cart Value | cart_value | Number | 0 | | Cart Abandoned | cart_abandoned | Boolean | false | | Cart Abandoned At | cart_abandoned_at | Date/Time | | | Last Product Added | last_product_added | Text | | | Cart Recovered | cart_recovered | Boolean | false | ## Step 3: Create Segments ### Segment 1: Abandoned Cart (1 hour) 1. Segments → New 2. Name: "Abandoned Cart - 1 Hour" 3. Filters: - Cart Abandoned = true - Cart Abandoned At > 1 hour ago - Cart Recovered = false - Email = not empty ### Segment 2: Abandoned Cart (24 hours) 1. Segments → New 2. Name: "Abandoned Cart - 24 Hours" 3. Filters: - Cart Abandoned = true - Cart Abandoned At > 1 day ago - Cart Recovered = false - Email = not empty ## Step 4: Create Email Templates ### Email 1: First Reminder (1 hour) **Subject:** Zaboravili ste nešto u korpi / You left something in your cart ```htmlPrimijetili smo da ste ostavili artikle u korpi:
Poslednji proizvod: {contactfield=last_product_added}
Vrednost korpe: {contactfield=cart_value} USD
We noticed you left items in your cart:
Last product: {contactfield=last_product_added}
Cart value: {contactfield=cart_value} USD
Vaša korpa još uvijek čeka! Dajemo vam 10% popusta da završite kupovinu:
Koristite kod: COMEBACK10
Završite kupovinu sa 10% popustaYour cart is still waiting! Here's 10% off to complete your purchase:
Use code: COMEBACK10
Complete Purchase with 10% Off ``` ## Step 5: Create Campaign ### Campaign Workflow 1. **Campaigns → New** 2. Name: "Abandoned Cart Recovery" 3. Description: "Recover abandoned carts with 2-email sequence" **Campaign Canvas:** ``` [Contact enters campaign] ↓ [Decision: Cart Abandoned?] ↓ Yes [Wait: 1 hour] ↓ [Send Email: First Reminder] ↓ [Wait: 23 hours] ↓ [Decision: Cart Recovered?] ↓ No [Send Email: Second Reminder + 10% off] ↓ [Wait: 3 days] ↓ [Decision: Cart Recovered?] ↓ No [Remove from campaign] ``` ### Campaign Settings **Entry Conditions:** - Contact added to segment "Abandoned Cart - 1 Hour" **Exit Conditions:** - Cart Recovered = true - Order Completed event triggered ## Step 6: Cart Recovery Page Create a recovery page in Next.js: ```typescript // pages/cart-recovery.tsx import { useEffect } from 'react'; import { useRouter } from 'next/router'; import { saleorClient } from '@/lib/saleor/client'; import { gql } from '@apollo/client'; import { markCartRecovered } from '@/lib/mautic-api'; const GET_CHECKOUT_BY_EMAIL = gql` query GetCheckoutByEmail($email: String!) { checkouts(first: 1, filter: {customer: $email}) { edges { node { id token lines { id quantity variant { id name product { name } } } } } } } `; export default function CartRecoveryPage() { const router = useRouter(); const { email, coupon } = router.query; useEffect(() => { if (email) { // Mark cart as recovered in Mautic markCartRecovered(email as string); // Redirect to checkout with recovered cart // You'll need to implement checkout restoration logic router.push(`/checkout?email=${email}${coupon ? `&coupon=${coupon}` : ''}`); } }, [email, coupon, router]); return (Restoring your cart...