Skip to main content
Technology & EngineeringEmail Services319 lines

Mailjet

Send transactional and marketing email with Mailjet. Use this skill when the project

Quick Summary33 lines
You are an expert in Mailjet for transactional and marketing email. You understand
the Send API v3.1, MJML-based template system, contact and list management, real-time
event webhooks, and how to leverage Mailjet for both transactional reliability and
marketing campaigns.

## Key Points

- **Exceeding the 50-message batch limit** -- Mailjet's v3.1 API limits each request to 50 messages. Sending more in a single call returns an error. Chunk recipients into batches of 50.
- **Skipping the TextPart in email sends** -- If you omit the plain-text version, Mailjet auto-generates one poorly. Always provide a real plain-text alternative alongside HTML.
- **Not URL-encoding email addresses in REST paths** -- When fetching contacts by email, the `@` character must be URL-encoded. Unencoded `@` breaks the REST path and returns 404.
- **Use the v3.1 Send API, not v3.** The v3.1 endpoint returns structured per-message
- **Always include TextPart.** Email clients that cannot render HTML fall back to
- **Use template variables, not string interpolation.** Pass dynamic data through
- **Chunk batch sends to 50 messages per call.** Mailjet's v3.1 API limits each
- **Configure event webhooks for every event type.** Set up webhooks in the Mailjet
- **Use contact properties for segmentation.** Store structured data as contact
- **Using v3 Send API by mistake.** If you omit `{ version: "v3.1" }` in the SDK,
- **Exceeding the 15 MB attachment limit.** Mailjet limits total message size to
- **Not URL-encoding contact email in REST paths.** When fetching a contact by email,

## Quick Example

```bash
npm install node-mailjet
```

```
MAILJET_API_KEY=your-public-api-key
MAILJET_SECRET_KEY=your-secret-key
MAILJET_WEBHOOK_URL=https://app.example.com/webhooks/mailjet
```
skilldb get email-services-skills/MailjetFull skill: 319 lines
Paste into your CLAUDE.md or agent config

Mailjet — Email Service

You are an expert in Mailjet for transactional and marketing email. You understand the Send API v3.1, MJML-based template system, contact and list management, real-time event webhooks, and how to leverage Mailjet for both transactional reliability and marketing campaigns.

Core Philosophy

Mailjet unifies transactional and marketing email under a single API and dashboard, which is its primary advantage. Instead of integrating one service for receipts and another for newsletters, Mailjet handles both with a consistent template system, unified event tracking, and shared contact management. This reduces operational overhead but demands discipline: you must still separate transactional and marketing sending identities at the domain level to protect deliverability.

Templates should live in Mailjet, not in your codebase. Mailjet's template system supports variables ({{var:firstName}}), conditional blocks, and MJML for responsive layouts. Keeping templates in the platform lets non-developers update copy, preview renders, and collaborate without code deploys. Your application code should pass structured data through the Variables field and let the template handle presentation.

Event webhooks are your window into deliverability. Configure webhooks for every event type -- sent, open, click, bounce, spam, unsub, blocked -- and process them reliably. Bounces and spam complaints require immediate suppression. Opens and clicks feed engagement metrics that inform future sends. Without webhooks, you are sending email into a void with no feedback loop on what is working and what is damaging your reputation.

Anti-Patterns

  • Using the v3 Send API instead of v3.1 -- The v3 endpoint is legacy with a different request/response format. Always use { version: "v3.1" } in the SDK for modern features and structured per-message status.
  • Omitting TemplateLanguage: true when using stored templates -- Without this flag, Mailjet sends the raw template without variable substitution, delivering broken placeholder text to recipients.
  • Exceeding the 50-message batch limit -- Mailjet's v3.1 API limits each request to 50 messages. Sending more in a single call returns an error. Chunk recipients into batches of 50.
  • Skipping the TextPart in email sends -- If you omit the plain-text version, Mailjet auto-generates one poorly. Always provide a real plain-text alternative alongside HTML.
  • Not URL-encoding email addresses in REST paths -- When fetching contacts by email, the @ character must be URL-encoded. Unencoded @ breaks the REST path and returns 404.

Overview

Mailjet is an email delivery platform offering both transactional and marketing email through a unified REST API. Key differentiators include native MJML support for responsive email templates, real-time collaboration on templates, built-in A/B testing, send-time optimization, and a contact segmentation engine. Mailjet provides both API and SMTP sending, with the v3.1 Send API supporting batch sends with per-recipient personalization in a single call.

Setup & Configuration

Installation

npm install node-mailjet

Client setup

import Mailjet from "node-mailjet";

const mailjet = Mailjet.apiConnect(
  process.env.MAILJET_API_KEY!,
  process.env.MAILJET_SECRET_KEY!
);

Environment variables

MAILJET_API_KEY=your-public-api-key
MAILJET_SECRET_KEY=your-secret-key
MAILJET_WEBHOOK_URL=https://app.example.com/webhooks/mailjet

Domain authentication

Add the DKIM and SPF records Mailjet provides in Settings > Sender Domains. Set up a custom return-path (bounce) domain for full alignment. Verify the domain to unlock higher sending limits:

// Programmatic sender validation (email-based)
const request = mailjet.post("sender").request({
  Email: "notifications@example.com",
  Name: "Example App",
});

Core Patterns

Send a single transactional email

async function sendTransactional(
  to: string,
  toName: string,
  subject: string,
  html: string
) {
  const result = await mailjet.post("send", { version: "v3.1" }).request({
    Messages: [
      {
        From: {
          Email: "app@example.com",
          Name: "Example App",
        },
        To: [{ Email: to, Name: toName }],
        Subject: subject,
        HTMLPart: html,
        TextPart: "", // always include a text fallback
      },
    ],
  });

  return result.body; // { Messages: [{ Status: "success", To: [...], MessageID, MessageUUID }] }
}

Send with a stored template and variables

async function sendFromTemplate(
  to: string,
  toName: string,
  templateId: number,
  variables: Record<string, unknown>
) {
  return mailjet.post("send", { version: "v3.1" }).request({
    Messages: [
      {
        From: { Email: "app@example.com", Name: "Example App" },
        To: [{ Email: to, Name: toName }],
        TemplateID: templateId,
        TemplateLanguage: true,
        Variables: variables,
      },
    ],
  });
}

// Usage — template uses {{var:firstName}} syntax
await sendFromTemplate("alice@example.com", "Alice", 12345, {
  firstName: "Alice",
  resetUrl: "https://app.example.com/reset?token=abc",
});

Batch send with per-recipient personalization

async function sendBatch(
  recipients: Array<{ email: string; name: string; variables: Record<string, unknown> }>,
  templateId: number
) {
  // Mailjet v3.1 supports up to 50 messages per API call
  const messages = recipients.map((r) => ({
    From: { Email: "app@example.com", Name: "Example App" },
    To: [{ Email: r.email, Name: r.name }],
    TemplateID: templateId,
    TemplateLanguage: true,
    Variables: r.variables,
  }));

  // Chunk into batches of 50
  const chunks = [];
  for (let i = 0; i < messages.length; i += 50) {
    chunks.push(messages.slice(i, i + 50));
  }

  const results = [];
  for (const chunk of chunks) {
    const result = await mailjet.post("send", { version: "v3.1" }).request({
      Messages: chunk,
    });
    results.push(result.body);
  }
  return results;
}

Manage contacts and lists

// Create a contact list
const list = await mailjet.post("contactslist").request({
  Name: "Newsletter Subscribers",
});

// Add a contact to a list
await mailjet.post("contactslist", { version: "v3" })
  .id(list.body.Data[0].ID)
  .action("managecontact")
  .request({
    Email: "alice@example.com",
    Name: "Alice",
    Action: "addnoforce", // add without overwriting existing
    Properties: {
      plan: "pro",
      signup_date: "2025-01-15",
    },
  });

// Remove from list (does not delete the contact)
await mailjet.post("contactslist", { version: "v3" })
  .id(listId)
  .action("managecontact")
  .request({
    Email: "alice@example.com",
    Action: "remove",
  });

Process event webhooks

import { Router, Request, Response } from "express";

const router = Router();

interface MailjetEvent {
  event: string;
  MessageID: number;
  email: string;
  mj_campaign_id?: number;
  error_related_to?: string;
  error?: string;
  source?: string;
  url?: string;
}

router.post("/webhooks/mailjet", (req: Request, res: Response) => {
  const events: MailjetEvent[] = Array.isArray(req.body)
    ? req.body
    : [req.body];

  for (const event of events) {
    switch (event.event) {
      case "sent":
        // Successfully delivered to recipient server
        break;
      case "open":
        // Recipient opened
        break;
      case "click":
        // Link clicked — event.url has the target
        break;
      case "bounce":
        // Hard bounce — suppress address
        break;
      case "blocked":
        // Mailjet blocked the send (suppression, policy)
        break;
      case "spam":
        // Spam complaint — suppress immediately
        break;
      case "unsub":
        // Unsubscribe via Mailjet's built-in link
        break;
    }
  }

  res.status(200).end();
});

Send via SMTP (alternative to API)

Host: in-v3.mailjet.com
Port: 587 (STARTTLS) or 465 (SSL)
Username: your MAILJET_API_KEY
Password: your MAILJET_SECRET_KEY
import nodemailer from "nodemailer";

const transport = nodemailer.createTransport({
  host: "in-v3.mailjet.com",
  port: 587,
  secure: false,
  auth: {
    user: process.env.MAILJET_API_KEY,
    pass: process.env.MAILJET_SECRET_KEY,
  },
});

Best Practices

  • Use the v3.1 Send API, not v3. The v3.1 endpoint returns structured per-message status responses and supports modern features. The v3 send endpoint is legacy.
  • Always include TextPart. Email clients that cannot render HTML fall back to plain text. If you omit TextPart, Mailjet auto-generates one, but the result is often poor. Write a real text version or use a library to convert HTML.
  • Use template variables, not string interpolation. Pass dynamic data through the Variables field so templates can be managed in the Mailjet UI. This separates content from code.
  • Chunk batch sends to 50 messages per call. Mailjet's v3.1 API limits each request to 50 messages. Exceeding this returns an error.
  • Configure event webhooks for every event type. Set up webhooks in the Mailjet dashboard for sent, open, click, bounce, spam, unsub, and blocked. This gives you full visibility into deliverability.
  • Use contact properties for segmentation. Store structured data as contact properties rather than embedding logic in your code. Let Mailjet's segment builder handle audience targeting.

Common Pitfalls

  • Using v3 Send API by mistake. If you omit { version: "v3.1" } in the SDK, it defaults to v3. The request and response formats differ, causing confusing errors.
  • Exceeding the 15 MB attachment limit. Mailjet limits total message size to 15 MB. For large attachments, host files externally and link to them.
  • Not URL-encoding contact email in REST paths. When fetching a contact by email, URL-encode the @ character or use the contact ID instead. Unencoded @ breaks the REST path.
  • Forgetting TemplateLanguage: true. If you reference a template by ID but omit TemplateLanguage: true, Mailjet sends the raw template without variable substitution.
  • Webhook endpoint returning non-200 status. Mailjet retries failed webhook deliveries. If your endpoint consistently fails, Mailjet disables the webhook. Always return 200 even if you defer processing to a queue.

Install this skill directly: skilldb add email-services-skills

Get CLI access →