On Tabeer.ai I needed transactional email fast — account verification, password resets, weekly progress digests — and the quickest path was a Gmail App Password against Django's SMTP backend. It's a genuinely common shortcut for a project that isn't ready to pay for a transactional email provider yet. It also has a failure mode that doesn't show up until it's already cost you real users, and that's the part worth writing down.
The Setup (This Part Really Is Five Minutes)
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_HOST_USER = "info.tabeerai@gmail.com"
EMAIL_HOST_PASSWORD = "xxxx xxxx xxxx xxxx" # Gmail App Password, not the account password
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = "Tabeer.ai <info.tabeerai@gmail.com>"
One detail that trips people up: DEFAULT_FROM_EMAIL has to match the authenticated Gmail account (or a verified alias on it). Gmail's relay will reject — or silently rewrite — a From address it doesn't recognize as belonging to the authenticating account. I initially wanted hello@tabeer.ai in that field; it doesn't work through a personal Gmail relay, only through the actual @gmail.com address or a properly configured "Send As" alias.
With that config, send_mail() returns 1 and raises nothing. Every test — locally, then on the production server — looked like a clean pass.
Where It Actually Fails
Days later, a bounce notification came back for a verification email:
550 5.7.30 ... DKIM authentication didn't pass ...
Gmail requires all email bulk senders to authenticate their email with DKIM
Not a one-off — a second bounce, for a different message, came in later the same way. This is the actual failure mode: SMTP-level success only confirms Gmail accepted the message for relay, not that it delivered. Gmail evaluates deliverability asynchronously, after your code has already moved on and logged success. A plain consumer @gmail.com account has no DKIM record you control — Google signs outbound mail with its own infrastructure key, which satisfies casual, low-volume personal email but fails Gmail's own bulk-sender authentication policy the moment your app starts sending anything at automation volume (verification emails, password resets, digests — exactly the transactional pattern most apps need).
This is the trap: your own test suite cannot catch this. send_mail() succeeding is not proof of delivery. If your only signal is "did the function raise," you will ship this and find out from a support ticket, not a test failure.
What Actually Fixes It
Two real options, both requiring domain-level authentication Gmail's consumer relay structurally can't give you:
- A transactional email provider (SendGrid, Postmark, Resend, AWS SES) with your own domain's SPF, DKIM, and DMARC DNS records configured. This is the standard fix, and most of these have generous free tiers for the volume a small app actually sends.
- Google Workspace, if you specifically want to keep sending "from" a Gmail-family address — a Workspace mailbox on your own domain lets you configure real DKIM signing for that domain, unlike a free consumer Gmail account.
If you're already on Cloudflare for DNS (I am, for this project), adding the SPF/DKIM/DMARC records for either option is a few TXT record API calls — the actual blocker is almost never the DNS, it's picking a provider and generating the DKIM keypair it gives you.
The Actual Lesson
"It sent without an error" and "it was delivered" are different claims, and for any provider with a serious bulk-sender policy — Gmail very much included — you cannot verify the second one synchronously. If transactional email matters for your product (and account verification usually blocks a user from ever activating), don't let a green checkmark from send_mail() be the only thing you check. Watch for bounce webhooks, or better, don't route production transactional volume through a personal Gmail account in the first place — it's fine for a prototype, and a real liability past that.
If you're debugging email deliverability on a Django or Nuxt project, or setting up SPF/DKIM/DMARC properly the first time, get in touch.
By Shahid Malik