Skip to main content
SahajMails

SahajMails

Personalized bulk email that runs on your machine.

Sahaj means simple and natural. So does this.

PyPI Python License CI

SahajMails Campaigns View

pip install sahajmails
sahajmails

That's it. A local web app opens. Load a spreadsheet, write your email, send.

Your contacts, your password and your drafts never leave your computer. There is no account, no server of ours, and no telemetry.


What it does

Works with your own mailbox Gmail, Outlook, Yahoo, Zoho, iCloud, Fastmail, SES, Mailgun, Postmark, SendGrid, Resend, or any SMTP server.
Real personalization {{ first_name }} from any column, case-insensitive, with defaults and conditionals.
Write how you like A formatted editor, plain formal text, Markdown, or your own HTML.
Optional AI, fully controlled The model fills one labelled gap. It never sees the rest of your email, and you review every line before it sends.
Never sends twice Every attempt is journalled. Crash, close the lid, lose Wi-Fi — resume and nobody is mailed again.
Lands in inboxes Plain-text alternative, List-Unsubscribe, preheader, SPF/DKIM/DMARC check, spam-content linter.
Tells you before you send Pre-flight renders every contact and reports what will break.
Extensible Plugins add transports, AI providers, contact sources and UI panels.

App Preview

Campaigns & Setup Execution & Logs
Settings configuration Run history
AI Personalization Plugins & Extensibility
AI integration Plugin management
Deliverability Suppression List
Suppressed emails Campaign dashboard

The 60-second version

pip install sahajmails
sahajmails                 # opens http://localhost:8000
  1. Settings → your address and an app password → Test connection
  2. New campaign → drop your CSV → write your email → watch the live preview
  3. Review → pre-flight + send yourself a test
  4. Send

Prefer a terminal? Everything is a subcommand:

sahajmails init                                   # starter files here
sahajmails check contacts.csv -t email.md -s "Hi {{ first_name }}"
sahajmails send  contacts.csv -t email.md -s "Hi {{ first_name }}" --dry-run

Or import it:

from sahajmails import send

send(contacts="contacts.csv", template="email.md", subject="Hello {{ first_name }}")

Writing the email

Your spreadsheet columns become placeholders. Casing and spacing don't matter — {{ firstName }}, {{ first_name }} and {{ FIRST_NAME }} are the same column.

Hi {{ first_name | default("there") }},

{% if company %}I saw the news about {{ company }} — congratulations.{% endif %}

The rest of this email is identical for everyone.

Best,
Sahaj

default(...) matters: without it, a blank cell stops the send rather than quietly mailing "Hi ,". That is deliberate.

AI personalization

Wrap the one sentence you want personalized. Everything outside the block is byte-identical for every recipient — that is enforced by the design, not by asking the model nicely.

Hi {{ first_name }},

{% ai "opener" max_words=25 fallback="Hope your week is going well." %}
One warm sentence about {{ company }}. No greeting, no exclamation marks.
{% endai %}

I'm writing because …
  • The model receives only that instruction and that contact's variables.
  • Output is capped, stripped of markup, and checked against your banned phrases.
  • Anything that fails becomes your fallback. A flaky API can never block a send.
  • You review every line before anything is sent, and your edits are used verbatim.

Works with Claude, OpenAI, or any OpenAI-compatible endpoint — including Ollama and LM Studio running locally, so the "nothing leaves your machine" promise stays true.


Documentation

Getting started Install, connect, first send
AI personalization Slots, review, cost, local models
Deliverability SPF, DKIM, DMARC, unsubscribe, bounces
Providers Every preset, limits, app passwords
CLI reference Every command and flag
Python API Using it as a library
Writing plugins A worked example
Security What runs where, and how it is protected
Troubleshooting When something goes wrong
Migrating from 1.x What changed and why

Security in one paragraph

The app binds 127.0.0.1 only. It is protected by a session token, a Host allowlist (which is what stops DNS-rebinding attacks that same-origin policy does not), CSRF headers, and a sandboxed template engine that escapes contact data so a spreadsheet can never inject markup into your mail. Passwords are kept in memory unless you explicitly ask to store them. Full detail in docs/security.md; report anything you find via SECURITY.md.


Contributing

git clone https://github.com/sahajrajmalla/sahajmails.git
cd sahajmails
make install     # needs pip >= 25.1
make check       # ruff, mypy --strict, pytest

See CONTRIBUTING.md. The whole test suite runs offline — there is a built-in SMTP stub and a fake AI backend, so you never need real credentials to develop.


License

MIT — free for personal and commercial use.

Built by Sahaj Raj Malla

Metadata

Release files for sahajmails 2.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sahajmails 2.0.2
File Size Uploaded
sahajmails-2.0.2.tar.gz 134.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sahajmails 2.0.2
File Interpreter ABI Platform
sahajmails-2.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 259.8 kB

Release files / sahajmails-2.0.2.tar.gz

Download URL sahajmails-2.0.2.tar.gz
Size 134.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f0446f29985a47a87da76208720c899bf4f124d54dd139dcf5e8f9e71b55d1e2
BLAKE2b-256 checksum
How to use checksums
b45e6f4800f28711ff2c414ea6abecdc6b5489f35dec5800fa24a864e3379234
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / sahajmails-2.0.2-py3-none-any.whl

Download URL sahajmails-2.0.2-py3-none-any.whl
Size 124.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8fb254a0ea252fd34f1cf9f6dabb7174f5a669da2c2bd5915ee0ce05a1698706
BLAKE2b-256 checksum
How to use checksums
17de76fe9451895c1627d18b1767f83c947c24915628e386240fb4138266e7b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release history Release notifications | RSS feed

This release

2.0.2 This release

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.0.1

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page