Development

How to code an email receipt template with MJML and Mailjet’s templating language

Learn how to code an e-receipt or email receipt template with MJML and templating language in this step by step tutorial, with code samples and examples.
Image for How to code an email receipt template with MJML and Mailjet’s templating language
August 29, 2025

Building production-ready email receipts can be challenging, especially when you need to handle multiple scenarios like guest vs. logged-in customers, different shipping methods, discounts, and multi-currency support. This comprehensive guide will show you how to create a responsive, dynamic email receipt template using MJML for layout and Mailjet’s templating language for email personalization.

By the end of this tutorial, you’ll have a fully functional receipt template that can handle complex order data, attach PDF invoices, and deliver reliably through Mailjet’s Send API v3.1.

Why use templates with templating language?

Before diving into the implementation, let’s understand why this approach is superior to creating one-off HTML emails:

  • Fewer templates to maintain: One “smart” receipt template can handle multiple scenarios (guest vs. logged-in users, shipping vs. pickup, discounts, multi-currency)
  • Server-side personalization: Inject dynamic data using {{var:*}} or {{data:*}} variables and control rendering with conditions and loops
  • Faster integration: Store the template once, then trigger it with a single API call per order

What you’ll build

Your final email receipt template will include:

  • Responsive MJML layout that works across all email clients
  • Dynamic line item loops for order details
  • Tax, discount, and total calculations
  • Conditional shipping information display
  • Localized text and currency formatting
  • Optional PDF invoice attachments
  • Reliable delivery through Mailjet’s Send API v3.1 with event tracking

Prerequisites

Before starting, ensure you have:

  • Created a Mailjet account with a validated sender domain (set up SPF/DKIM for optimal deliverability)
  • An MJML workflow (CLI or editor) to compile MJML to HTML
  • Mailjet API credentials (API key and secret for HTTP Basic Auth)
  • Optional: A webhook endpoint to receive delivery events (sent, open, click, bounce, etc.)

Step 1: Structure the receipt with MJML

Let’s start by creating the basic structure of our receipt template using MJML. This will ensure our email is responsive and renders consistently across different email clients.

                            

                                <mjml>
  <mj-head>
    <mj-title>Your receipt</mj-title>
    <mj-attributes>
      <mj-text font-family="Montserrat, Arial, sans-serif" />
      <mj-class name="muted" color="#6b7280" font-size="13px" />
      <mj-class name="total" font-weight="700" font-size="16px" />
    </mj-attributes>
  </mj-head>
  <mj-body background-color="#f6f7fb">
    <mj-section background-color="#ffffff" padding="24px">
      <mj-column>
        <mj-image width="120px" src="https://yourcdn.com/logo.png" alt="Brand" />
        <mj-text font-size="20px" font-weight="700">Thanks for your purchase!</mj-text>
        <mj-text css-class="muted">
          Order {{var:order_id}} • {{var:order_date}} • {{var:currency}}
        </mj-text>

        <!-- Shipping notice (rendered conditionally in Step 2) -->
        <mj-text css-class="muted">
          Delivery to: {{var:shipping.name}}, {{var:shipping.address1}}, {{var:shipping.city}}
        </mj-text>

        <mj-divider border-color="#e5e7eb" />

        <!-- Line items table (rendered via loop in Step 2) -->
        <mj-table>
          <tr style="text-align:left;">
            <th style="padding:8px 0;">Item</th>
            <th style="padding:8px 0;">Qty</th>
            <th style="padding:8px 0; text-align:right;">Price</th>
          </tr>
          <!-- row loop here -->
        </mj-table>

        <mj-divider border-color="#e5e7eb" />

        <mj-text>
          Subtotal <span style="float:right;">{{var:money.subtotal}}</span>
        </mj-text>
        <mj-text>
          Tax <span style="float:right;">{{var:money.tax}}</span>
        </mj-text>
        {{var:money.discount_row}} <!-- optional discount row via condition -->
        <mj-text css-class="total">
          Total <span style="float:right;">{{var:money.total}}</span>
        </mj-text>

        <mj-divider border-color="#e5e7eb" />
        <mj-text css-class="muted">
          Paid with {{var:payment.brand}} ending {{var:payment.last4}}. Need a PDF? It's attached.
        </mj-text>
        <mj-text css-class="muted">
          Questions? Reply to this email or visit your order page: {{var:order_url}}
        </mj-text>
      </mj-column>
    </mj-section>

    <mj-section>
      <mj-column>
        <mj-text css-class="muted" align="center">
          © {{var:year}} Your Company • {{var:support_email}}
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

                            
                        

Important Notes:

  • Keep currency formatting on the server-side and pass ready-to-render strings (money.*) to avoid locale inconsistencies
  • MJML compiles to responsive HTML that works across major email clients

Step 2: Add Mailjet templating tags

Now we’ll enhance our template with Mailjet’s templating language to handle dynamic content. This includes variables, conditions, and loops that will be processed when the email is sent.

Variable types

Mailjet’s templating language supports several variable types:

  • Order variables: {{var:order_id}}, {{var:currency}}, {{var:payment.brand}}
  • Contact data: {{data:firstname:”Customer”}} (with fallback values)
  • Conditions: {% if var:has_shipping %} … {% endif %}
  • Loops: {% for item in var:items %} … {% endfor %}

Adding dynamic line items

Insert this code inside your MJML table to loop through order items:

                            

                                {% for item in var:items %}
<tr>
  <td style="padding:6px 0;">{{item.name}}</td>
  <td style="padding:6px 0;">{{item.qty}}</td>
  <td style="padding:6px 0; text-align:right;">{{item.price}}</td>
</tr>
{% endfor %}

                            
                        

Conditional shipping information

Add conditional blocks to show shipping details only when relevant:

                            

                                {% if var:has_shipping %}
<mj-text css-class="muted">
  Delivery to: {{var:shipping.name}}, {{var:shipping.address1}}, {{var:shipping.city}}
</mj-text>
{% endif %}
                            
                        

Step 3: Save the HTML as a reusable template

Once you’ve compiled your MJML to HTML and added templating tags, you need to save it as a template in Mailjet. You have two options:

Option A: Using the Mailjet dashboard

Paste your compiled HTML into a new template in the Mailjet app (Passport).

Option B: Using the template API

Create a new template:

                            

                                curl -s -X POST \
  --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \
  https://api.mailjet.com/v3/REST/template \
  -H 'Content-Type: application/json' \
  -d '{
    "Name":"Receipt v1",
    "OwnerType":"user",
    "IsTextPartGenerationEnabled":"true",
    "Locale":"en_US"
  }'

                            
                        

Add your HTML content to the template:

                            

                                curl -s -X POST \
  --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \
  https://api.mailjet.com/v3/REST/template/TEMPLATE_ID/detailcontent \
  -H 'Content-Type: application/json' \
  -d '{
    "Html-part":"<html>...your compiled MJML with templating tags...</html>",
    "Text-part":"Your plain text fallback"
  }'
                            
                        

Step 4: Send the receipt with Send API v3.1

Now comes the exciting part – sending your dynamic receipt email. Here’s a complete example that includes template variables, PDF attachment, and proper configuration:

                            

                                curl -s -X POST \
  --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \
  https://api.mailjet.com/v3.1/send \
  -H 'Content-Type: application/json' \
  -d '{
    "Messages":[
      {
        "From":{"Email":"billing@yourbrand.com","Name":"Your Brand Billing"},
        "To":[{"Email":"customer@example.com","Name":"{{data:firstname:\"Customer\"}}"}],
        "TemplateID": TEMPLATE_ID,
        "TemplateLanguage": true,
        "Subject": "Your receipt {{var:order_id}}",
        "Variables": {
          "order_id": "ORD-98765",
          "order_date": "2025-08-27",
          "currency": "USD",
          "items": [
            {"name":"Sneakers","qty":1,"price":"$99.00"},
            {"name":"Socks","qty":2,"price":"$10.00"}
          ],
          "money":{"subtotal":"$119.00","tax":"$9.52","discount":"$0.00","total":"$128.52"},
          "has_shipping": true,
          "shipping":{"name":"A. Smith","address1":"10 Market St","city":"Austin"},
          "payment":{"brand":"Visa","last4":"4242"},
          "order_url":"https://yourapp.com/orders/ORD-98765",
          "support_email":"support@yourbrand.com",
          "year":"2025"
        },
        "Attachments":[
          {
            "ContentType":"application/pdf",
            "Filename":"Invoice-ORD-98765.pdf",
            "Base64Content":"<base64-encoded-pdf>"
          }
        ]
      }
    ]
  }'

                            
                        

Useful send options

  • Globals: Avoid repetition across bulk sends (From, Subject, headers)
  • SandboxMode: Set to true to validate payload without sending
  • CustomID: Use “order-ORD-98765” to correlate events
  • URLTags: Add “utm_source=receipt&utm_medium=email” for tracking

URLTags: Add “utm_source=receipt&utm_medium=email” for tracking

Step 5: Verify delivery and track events

Monitoring your email delivery is crucial for maintaining customer trust and debugging issues.

Setting up webhooks

Register your webhook endpoint to receive real-time events:

  • Sent, delivered, opened, clicked
  • Bounced, spam complaints, blocked
  • Unsubscribed

Checking message status

  • Message info: GET /v3/REST/message/{Message_ID} for metadata
  • Event timeline: GET /v3/REST/messagehistory/{Message_ID} for detailed events
  • Statistics: Use /v3/REST/statcounters for aggregated delivery metrics

Step 6: Internationalization and currency

For global businesses, proper localization is essential:

  • Currency formatting: Format amounts server-side for the recipient’s locale and pass ready-to-render strings in Variables (e.g., money.total = “129,90 €”)
  • Fallback text: Use {{data:firstname:”Client”}} for language-aware fallbacks
  • Multi-language templates: Either maintain one template with conditional blocks ({% if var:locale == “fr” %}…{% endif %}) or create separate templates per locale

Step 7: Deliverability and compliance

Ensure your receipts reach the inbox and comply with regulations:

Technical setup

  • Verify your domain and configure SPF/DKIM records
  • Send from your verified domain for best reputation
  • Keep templates lightweight with compressed images and minimal external fonts
  • Always provide a plain text version

Security and privacy

  • Never include full payment card numbers – use last 4 digits and brand only
  • Secure PDF attachments and minimize PII
  • Respect unsubscribe preferences for any promotional content

Step 8: Advanced production tips

For teams deploying at scale, consider these optimizations:

Attachment strategies

  • Use Attachments for PDFs that users download
  • Use InlinedAttachments for logos referenced via cid: in HTML

Campaign organization

  • Set CustomCampaign: “transactional_receipts” for better reporting
  • Enable DeduplicateCampaign to prevent duplicate sends in batches

Error handling and observability

  • Log Send API errors with specific codes and fields for debugging
  • Tag each send with CustomID for easy correlation
  • Include EventPayload with order metadata for downstream reconciliation

Troubleshooting common issues

Here are some issues to troubleshoot if you run into problems with the above.

Template variables not rendering

  • Ensure TemplateLanguage: true is set in your Send API call
  • Verify variable names in your template match those in the Variables object

Authentication errors

  • “Sender not authorized”: Validate your From domain/address first
  • Check that your API credentials are correct and have proper permissions

Missing content

  • “At least HTMLPart, TextPart or TemplateID must be provided”: Ensure you’re providing a TemplateID

Event tracking issues

  • Confirm your webhook endpoint returns HTTP 200
  • Check for rate limiting on your webhook endpoint
  • Consider using grouped events for high-volume scenarios

Nexts steps

You now have a complete, production-ready email receipt system! Here are some ways to enhance it further:

  • Implement A/B testing for different receipt designs
  • Add more sophisticated conditional logic for different product types
  • Set up automated monitoring and alerting for delivery issues
  • Explore Mailjet’s statistics API for detailed performance analytics

For additional resources and code samples, check out the Mailjet templating samples repository which includes a complete working example you can use as a starting point.

Summary

This guide walked you through creating a professional email receipt template using MJML for responsive design and Mailjet’s templating language for dynamic content. You learned how to handle complex order data, attach PDF invoices, ensure reliable delivery, and monitor performance; all essential components for a production ecommerce email system that scales with your business.