Skip to main content
SahajMails

SahajMails v2.0.0

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.0

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.0
File Size Uploaded
sahajmails-2.0.0.tar.gz 136.7 kB Details

Built distribution (wheel)

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

Total release size: 261.6 kB

Release files / sahajmails-2.0.0.tar.gz

Download URL sahajmails-2.0.0.tar.gz
Size 136.7 kB
Tags Source
SHA-256 checksum
How to use checksums
7a7e27cc4c24b9f994d39cf01b08f90e5e0a6795a739cdf060d79617b038d623
BLAKE2b-256 checksum
How to use checksums
39d93ff1b5c655afcc1a70b96cde0cdfa4a581e03f921c497fe75059cb45105b
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.0-py3-none-any.whl

Download URL sahajmails-2.0.0-py3-none-any.whl
Size 124.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
908e060d74748a2816e556840b9d60ec4adee6807e37e15b71bc1403dfd90ade
BLAKE2b-256 checksum
How to use checksums
071ba4b91e94e5ffa932a8701a3593b559f88136195e63fc3510cbbe976509ae
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

2.0.2

2 release files

2.0.1

2 release files

This release

2.0.0 This release

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