Metadata-Version: 2.4
Name: sahajmails
Version: 2.0.2
Summary: Simple, secure bulk email — a local tool and Python library with optional AI personalization
Author-email: Sahaj Raj Malla <mallasahajraj@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/sahajrajmalla/sahajmails
Project-URL: Repository, https://github.com/sahajrajmalla/sahajmails
Project-URL: Documentation, https://github.com/sahajrajmalla/sahajmails/blob/main/docs/getting-started.md
Project-URL: Changelog, https://github.com/sahajrajmalla/sahajmails/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/sahajrajmalla/sahajmails/issues
Keywords: email,bulk-email,email-marketing,smtp,gmail,personalization,mail-merge,ai,fastapi
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: End Users/Desktop
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Office/Business
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi>=0.110
Requires-Dist: uvicorn[standard]>=0.27
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: jinja2>=3.1
Requires-Dist: httpx>=0.27
Requires-Dist: click>=8.1
Requires-Dist: openpyxl>=3.1
Requires-Dist: markdown>=3.5
Requires-Dist: dnspython>=2.6
Provides-Extra: keyring
Requires-Dist: keyring>=24.0; extra == "keyring"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.40; extra == "anthropic"
Provides-Extra: openai
Requires-Dist: openai>=1.40; extra == "openai"
Dynamic: license-file

<div align="center">

<img src="https://raw.githubusercontent.com/sahajrajmalla/sahajmails/main/sahajmails/server/static/img/logo.svg" width="72" alt="SahajMails">

# SahajMails

**Personalized bulk email that runs on your machine.**

*Sahaj* means simple and natural. So does this.

[![PyPI](https://img.shields.io/pypi/v/sahajmails?color=0e8f5d)](https://pypi.org/project/sahajmails/)
[![Python](https://img.shields.io/badge/python-3.11%2B-0e8f5d)](https://python.org)
[![License](https://img.shields.io/badge/license-MIT-0e8f5d)](LICENSE)
[![CI](https://github.com/sahajrajmalla/sahajmails/actions/workflows/ci.yml/badge.svg)](https://github.com/sahajrajmalla/sahajmails/actions)

<img src="https://raw.githubusercontent.com/sahajrajmalla/sahajmails/main/assets/campaigns.png" width="800" alt="SahajMails Campaigns View">

</div>

---

```bash
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 |
| :---: | :---: |
| <img src="https://raw.githubusercontent.com/sahajrajmalla/sahajmails/main/assets/settings.png" width="400" alt="Settings configuration"> | <img src="https://raw.githubusercontent.com/sahajrajmalla/sahajmails/main/assets/runs.png" width="400" alt="Run history"> |
| **AI Personalization** | **Plugins & Extensibility** |
| <img src="https://raw.githubusercontent.com/sahajrajmalla/sahajmails/main/assets/ai_settings.png" width="400" alt="AI integration"> | <img src="https://raw.githubusercontent.com/sahajrajmalla/sahajmails/main/assets/plugins.png" width="400" alt="Plugin management"> |
| **Deliverability** | **Suppression List** |
| <img src="https://raw.githubusercontent.com/sahajrajmalla/sahajmails/main/assets/suppressed.png" width="400" alt="Suppressed emails"> | <img src="https://raw.githubusercontent.com/sahajrajmalla/sahajmails/main/assets/campaigns.png" width="400" alt="Campaign dashboard"> |

---

## The 60-second version

```bash
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:

```bash
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:

```python
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](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/getting-started.md) | Install, connect, first send |
| [AI personalization](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/ai-personalization.md) | Slots, review, cost, local models |
| [Deliverability](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/deliverability.md) | SPF, DKIM, DMARC, unsubscribe, bounces |
| [Providers](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/providers.md) | Every preset, limits, app passwords |
| [CLI reference](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/cli-reference.md) | Every command and flag |
| [Python API](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/python-api.md) | Using it as a library |
| [Writing plugins](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/writing-plugins.md) | A worked example |
| [Security](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/security.md) | What runs where, and how it is protected |
| [Troubleshooting](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/troubleshooting.md) | When something goes wrong |
| [Migrating from 1.x](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/migrating-from-1.x.md) | 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](https://github.com/sahajrajmalla/sahajmails/blob/main/docs/security.md); report anything you find via
[SECURITY.md](https://github.com/sahajrajmalla/sahajmails/blob/main/SECURITY.md).

---

## Contributing

```bash
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](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](LICENSE) — free for personal and commercial use.

<div align="center">
<sub>Built by <a href="https://github.com/sahajrajmalla">Sahaj Raj Malla</a></sub>
</div>
