Mailjet Setup Pitfall Guide: SPF, DKIM, DMARC Deliverability
Here’s the full English version of this article, keeping the same structure and “real-pitfalls + step-by-step” tone.
This is a step-by-step guide that collects all the real issues I ran into when integrating Mailjet.
Goal: send transactional emails only, deliver reliably to the inbox, and avoid sending limits / suspensions.
0. Pre-Launch Checklist (Quick View)
- Account has passed manual review / is unsuspended (Business Verification submitted and clearly states “transactional emails only”)
- You only use your own domain for From addresses (e.g.
noreply@yourdomain.com), nevergmail.com / outlook.cometc. - SPF is a single record only, and includes
include:spf.mailjet.com - DKIM is active (
mailjet._domainkeyas a TXT record, value is a single-line public key) - DMARC is enabled (
_dmarcTXT record, even if policy is justp=noneat first) - SMTP or Send API is working with a minimal sending example
- Webhook is configured (you can see bounces, blocks, soft bounces, opens, clicks, etc.)
- Real-world test with Gmail/Outlook shows: SPF=Pass / DKIM=Pass / DMARC=Pass in headers
1. Account Review & Unblocking (Business Verification)
1.1 Create the account first, then provide extra info
New accounts often see “Sending activity is suspended”.
This is usually not a config error; it means they need more business information and manual review.
1.2 What to tell support (key points)
- We send transactional emails only (e.g. task assignment notifications, system alerts, status change notifications). No marketing, no bulk campaigns, no purchased lists.
- Estimate of monthly sending volume (be conservative, e.g. 500–1,000 / month).
- Recipient sources: internal staff or existing users from our own system, all are work-related notifications.
- Attach a few sample emails (subject, recipient type, business trigger).
- Link to your website / product.
This type of description usually speeds up the manual review.
It’s normal if the case gets passed to another team; just wait it out.
2. Only Use Your Own Domain as Sender (Avoid Free Mailboxes)
On the Sender domains & addresses page you’ll see:
- Domains like
gmail.comcannot have SPF/DKIM configured by you. Mailjet explicitly does not recommend or allow them as sending domains. This severely hurts deliverability. - Correct approach: add and authenticate your own domain (e.g.
yourdomain.com), then use addresses such asnoreply@yourdomain.com,alerts@yourdomain.comas From.
3. DNS Authentication (Cloudflare Hands-On)
Tool: Cloudflare dashboard → DNS
Goal: exactly ONE SPF record, ONE DKIM record, ONE DMARC record.
3.1 SPF (must be a single TXT record)
Pitfall #1: Duplicate SPF records → SPF is considered invalid.
You may already have SPF for Google/M365 or Cloudflare Email Routing. Adding Mailjet on top easily leads to multiple records.
Merge rule: put all necessary include: mechanisms into one single SPF record, and keep only one ~all (or -all) at the end.
Example: you need Mailjet + Cloudflare Email Routing + Google:
Name: @
Type: TXT
Value: v=spf1 include:spf.mailjet.com include:_spf.mx.cloudflare.net include:_spf.google.com ~all
Notes:
- Order does not matter, but there must be only one SPF record.
- Avoid duplicate
include:entries, and only one~allat the end. - Once active, Mailjet’s SPF check should show OK.
3.2 DKIM (TXT, pay attention to “single line”)
In Mailjet’s Domain Authentication Setup you will see:
- Hostname:
mailjet._domainkey(it becomesmailjet._domainkey.yourdomain.com) - Type: TXT
- Value: a long public key starting with
k=rsa; p=MIIBI...
Pitfall #2: Pasting multi-line values or adding quotes incorrectly in Cloudflare → looks correct in UI, but actually invalid.
- Fix: remove line breaks and ensure the value is one single line.
- Cloudflare will display quotes for you; you do not need to add extra quotes manually.
Example (do not break this line):
Name: mailjet._domainkey
Type: TXT
Value: k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...QIDAQAB
3.3 DMARC (start with p=none)
Even if you don’t enforce anything at first, it’s worth collecting reports to see if anyone is abusing your domain:
Name: _dmarc
Type: TXT
Value: v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; sp=none; adkim=s; aspf=s
Later you can move p=none → quarantine → reject.
3.4 Other Cloudflare Gotchas
- Root CNAME: Cloudflare supports CNAME flattening at the root for web; it does not interfere with email TXT/MX records.
- Orange cloud proxy: TXT records for SPF/DKIM/DMARC are always DNS only, not proxied.
- TTL: Auto or 5 minutes is fine. After editing, wait a couple of minutes and then hit Refresh in Mailjet.
4. Sending Channel: SMTP vs Send API (Either or Both)
4.1 SMTP (fastest way to start)
- Dashboard path:
Account → SMTP and SEND API settings - Credentials:
Host: in-v3.mailjet.com,Port: 587 (STARTTLS)or465 (SSL) - Auth: use Mailjet API Key as username, Secret Key as password
Example (Node.js + nodemailer):
import nodemailer from "nodemailer";const transporter = nodemailer.createTransport({host: "in-v3.mailjet.com",port: 587,secure: false, // 587 with STARTTLSauth: {user: process.env.MJ_APIKEY_PUBLIC,pass: process.env.MJ_APIKEY_PRIVATE}
});await transporter.sendMail({from: "noreply@yourdomain.com",to: "employee@yourdomain.com",subject: "Task Assigned #12345",text: "A new work package has been assigned to you.",html: "<p>A new work package has been assigned to you.</p>"
});
4.2 Send API (more flexible, better for structured templates)
Use HTTP POST to https://api.mailjet.com/v3.1/send with Basic Auth (API Key / Secret):
curl -X POST \-u "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \-H "Content-Type: application/json" \https://api.mailjet.com/v3.1/send \-d '{"Messages": [{"From": {"Email": "noreply@yourdomain.com", "Name": "Your App"},"To": [{"Email": "employee@yourdomain.com", "Name": "John"}],"Subject": "Task Completed - #67890","TextPart": "A meter reading task has been completed.","HTMLPart": "<strong>Reading #67890</strong> completed."}]}'
Best practices
- For transactional email, send one recipient per message; don’t stuff many employees into the same To/CC/BCC.
- Maintain templates in Mailjet Transactional templates and treat them like versioned artifacts.
- Use only the domain you have authenticated for all From addresses.
5. Event Webhooks & Observability
Path: Account → Event notifications (webhooks)
Common events: sent, delivered, open, click, bounce, blocked, spam, unsub (for pure transactional flows you usually won’t see unsub).
Recommendations:
- Point to a backend endpoint like
/mailjet/events, validate the source, and log the raw payload. - Build a small “delivery timeline” view: for each message, show send/deliver/open/click/bounce events. This makes debugging much easier.
- For soft bounces (temporary failures), implement exponential backoff retries (e.g. 5m → 15m → 1h → 6h).
6. Verification & Troubleshooting
6.1 Quick self-test
-
Send an email to your own Gmail/Outlook address.
-
View original headers (Gmail → three dots → “Show original”):
SPF: PASSDKIM: PASS(d= should beyourdomain.comor the Mailjet DKIM domain)DMARC: PASS(based on the previous two)
-
If
SPF=softfailorDKIM=neutral, go back to Step 3 and double-check DNS.
6.2 Common problems list
- Multiple SPF records → merge into a single record; keep only one
~all/-all. - DKIM value with line breaks / wrong quoting → store as a single line of plain text.
- Using a free mailbox as From → switch to your own domain.
- Sending before domain verification is done → leads to throttling or blocking.
- Transactional email looks like marketing → keep it simple: mostly plain text + light HTML, like a system notification.
- Aggressive retry behaviour → temporary rejections get worse if you hammer the recipient; use exponential backoff.
- SMTP auth uses wrong credentials → make sure
user/passare API Key / Secret, not your login email password.
7. “Gray Release” Before Full Production
- Start with a small internal group for a week: monitor delivery, open rate, bounce rate, and blocked events.
- Throttling: even with low volumes, keep a reasonable per-minute / per-hour rate, avoid traffic spikes that might look suspicious to ISPs.
- If the account is limited again: reuse the Section 1 template, explain transactional use, examples, recipient source, and actual sending frequency.
8. Advanced: Sub-Accounts & Environment Separation
- Use Subaccounts or separate API Keys to isolate production vs test and different product lines. Webhooks can point to different endpoints per key.
- Subject naming convention:
[ENV] Task Assigned #12345(clearly labeled for non-production). - Template variables such as
{{name}},{{task_id}},{{date}}should be supplied by the backend. Template publishing should go through code review.
9. Minimal Working Configuration (Checklist)
Cloudflare DNS
@ TXT v=spf1 include:spf.mailjet.com include:_spf.mx.cloudflare.net include:_spf.google.com ~all
mailjet._domainkey TXT k=rsa; p=MIIBIjANBgkq...QIDAQAB // single line
_dmarc TXT v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; adkim=s; aspf=s
Sender
From: noreply@yourdomain.com
Sending
- SMTP:
in-v3.mailjet.com:587(STARTTLS) - Or Send API:
POST /v3.1/send(Basic Auth)
Webhook
/mailjet/events ← subscribe to sent, delivered, open, click, bounce, blocked, spam
10. FAQ
Q: SPF says “multiple SPF records, invalid”, but I need Google + Cloudflare + Mailjet. What now?
A: Combine all include: entries into one SPF record, e.g.:
v=spf1 include:spf.mailjet.com include:_spf.mx.cloudflare.net include:_spf.google.com ~all
Q: DKIM page says “DomainKey invalid”, but Cloudflare shows it saved correctly.
A: 90% of the time the value got split into multiple lines or segments. Change it to a single line value, save, then hit Refresh in Mailjet a few minutes later.
Q: Do transactional emails need an unsubscribe link?
A: True transactional notifications (strongly tied to work / system operations) do not require an unsubscribe link, but you must ensure you only send to recipients with an existing business relationship.
Q: Gmail still puts messages into Promotions or Spam?
A: For transactional messages, try:
- Mostly plain text with light HTML
- Minimal external links / images / tracking elements
- Honest, non-marketing subject lines
- From and Reply-To using the same or consistent domain
- All three: SPF / DKIM / DMARC must PASS
Closing
If you follow these steps, you can flatten most of the tricky parts of Mailjet integration in one go:
- Explain account usage clearly (transactional only)
- DNS trio set up correctly (single SPF, single-line DKIM, DMARC enabled)
- Get a minimal SMTP / Send API example working first, then add templates and webhooks
- Run a small-scale gray release + metrics dashboard so you can quickly trace any problems
Happy sending!
