Skip to main content

amagi-crm

A CRM for small firms that you host yourself: contacts, a Kanban deal pipeline, tasks and appointment booking. You get a web UI, a REST API and a CLI. Client details are encrypted in the database, and every change, read and export goes into an audit log that shows if anyone edits it later. It runs on SQLite with one command, or on PostgreSQL with Docker Compose.

Demo

demo

$ amagi-crm import contacts.csv
Ignored columns: Source, Date Added
Imported 8 contacts from contacts.csv: 1 duplicate emails skipped, 1 invalid rows.
  row 11: not a valid email address: 'not-an-email'

$ amagi-crm deals board
Lead (1, $12,000)
  #1    Roof repair contract                $12,000
Qualified (0, $0)
Proposal (0, $0)
Negotiation (0, $0)
Won (1, $4,500)
  #2    Practice website                     $4,500
Lost (0, $0)

$ amagi-crm book "2030-11-04 10:15" --title "Second meeting"
amagi-crm: that time overlaps another booked appointment

$ amagi-crm audit verify
Audit chain OK: 14 entries, head 46b0dbd8e9d9...

(The audit head hash is shortened here. The tool prints all 64 characters, and yours will differ because the chain is keyed to your own secret.)

The pipeline board in the web UI

The calendar: the day's appointments, tasks due and open slots

Install

pipx install amagi-crm

You need Python 3.10 or newer. To run on your own PostgreSQL server outside Docker, add the driver with pipx inject amagi-crm "psycopg[binary]" and set DATABASE_URL=postgresql+psycopg://... in .env.

Quick start (SQLite, one machine)

$ amagi-crm init --timezone America/New_York --hours "mon-fri 09:00-17:00"
Wrote .env with new encryption keys. Back it up somewhere safe.
Created admin user 'admin'. API token (shown once, store it now):
  acrm_JLf8AqWp...
Next: amagi-crm serve, then open http://127.0.0.1:8000

$ amagi-crm serve

Open http://127.0.0.1:8000 and sign in with that token. The API docs are at /docs.

init writes a .env file with your encryption keys. Treat that file like a password. If you lose the keys, the encrypted fields can't be recovered. Back up .env and the database to different places, because anyone holding both can read everything.

Docker Compose (PostgreSQL)

cp .env.example .env
docker build -t amagi-crm . && docker run --rm amagi-crm keygen --postgres >> .env
docker compose up -d
docker compose exec app amagi-crm user add admin --role admin

keygen prints fresh keys and a database password and writes nothing itself. The >> appends them to .env. The app listens on 127.0.0.1:8000 only (change the port with AMAGI_CRM_PORT). Put a TLS reverse proxy such as Caddy or nginx in front of it before anyone connects over a network.

Usage

Every command reads .env in the current folder, or the file you name with --env-file before the command. Without the keys it exits with an error instead of writing plaintext.

Import contacts

Import from another CRM's CSV export. Common headers map on their own (First Name, E-mail Address, Mobile Phone, Business Name, Tags and so on). A dry run makes the same checks and writes nothing:

$ amagi-crm import contacts.csv --dry-run
Ignored columns: Source, Date Added
Dry run, nothing written. Would import 8 contacts from contacts.csv: 1 duplicate emails skipped, 1 invalid rows.
  row 11: not a valid email address: 'not-an-email'

Drop --dry-run to import for real. Rows with a bad email are skipped and listed by line number, and an email already in the CRM (or repeated in the file) counts as a duplicate:

$ amagi-crm import contacts.csv
Ignored columns: Source, Date Added
Imported 8 contacts from contacts.csv: 1 duplicate emails skipped, 1 invalid rows.
  row 11: not a valid email address: 'not-an-email'

Find contacts

Search by name, company, email or phone with --search, or filter by tag:

$ amagi-crm contacts --tag client
id  name          company          email              phone     tags
--  ------------  ---------------  -----------------  --------  ---------------
3   Amara Okafor  Okafor Design    amara@example.org  555-0142  client
5   Priya Shah    Shah Accounting  priya@example.net  555-0144  client,referral

Work the pipeline

The stages are Lead, Qualified, Proposal, Negotiation, Won and Lost. Moving a deal to Won or Lost records the close date.

$ amagi-crm deals add "Roof repair contract" --amount 12000 --contact-email lee@example.com
Added deal #1: Roof repair contract ($12,000)

$ amagi-crm deals add "Practice website" --amount 4500 --contact-email dana.whitfield@example.com
Added deal #2: Practice website ($4,500)

$ amagi-crm deals move 2 Won
Moved deal #2 'Practice website' to Won.

$ amagi-crm deals summary
Pipeline, 2026-10-19 to 2026-10-25
  New deals     2     $16,500
  Won           1      $4,500
  Lost          0          $0
  Open now      1     $12,000
    Lead          1     $12,000
    Qualified     0          $0
    Proposal      0          $0
    Negotiation   0          $0

deals summary --json gives the same report for a script or a weekly email.

Tasks, bookings and the agenda

Bookings never overlap. The agenda lists the open slots inside working hours.

$ amagi-crm tasks add "Send the signed proposal" --due tomorrow --contact-email lee@example.com
Added task #1: Send the signed proposal (due 2026-10-21)

$ amagi-crm book "2030-11-04 10:00" --title "Site walk-through" --contact-email lee@example.com
Booked #1: Site walk-through, 2030-11-04 10:00 to 10:30

$ amagi-crm agenda --date 2030-11-04
Monday 04 November 2030 (America/New_York)
  10:00-10:30  Site walk-through
Tasks due:
  #1 Send the signed proposal (overdue, due 2026-10-21)
Open slots: 09:00, 09:30, 10:30, 11:00, 11:30, 12:00, 12:30, 13:00, 13:30, 14:00, 14:30, 15:00, 15:30, 16:00, 16:30

Users and calendar feeds

Roles are admin, member (read and write) and readonly. Each user can get a private .ics link to subscribe to from Google Calendar or Outlook. The feed shows only "Busy" unless you set AMAGI_CRM_FEED_DETAILS=1.

amagi-crm user add sam --role member
amagi-crm user feed-token sam --base-url https://crm.example.com

Public booking page

Set AMAGI_CRM_PUBLIC_BOOKING=1 and /book lets visitors pick an open slot without signing in. Each IP address gets 5 booking attempts per hour.

Run amagi-crm --help for the rest: contacts add, tasks done, export, user list, user revoke.

Encryption and the audit log

What's encrypted

The app encrypts these fields with Fernet (AES-128-CBC plus HMAC-SHA256) before anything reaches the database: contact names, emails, phones, companies, tags and notes, the titles and notes of deals, tasks and appointments, and appointment locations. Amounts, stages, dates and IDs stay in plaintext so the board can total and sort them. Emails also get a keyed HMAC blind index, so duplicates are caught without storing the address in the clear. Open the database in any SQLite or PostgreSQL client and a contact's email column holds a Fernet token starting gAAAAA, next to a 64-character hex email_index.

Key rotation

Generate a new key with amagi-crm keygen --key-only and put it first in AMAGI_CRM_ENCRYPTION_KEYS, keeping the old key after it. Then run amagi-crm rotate-keys. Once a second run reports 0 rows, back up and remove the old key.

Audit log

Every create, update, delete, move, read of a single record, import and export is logged with who, when, what and the field names (never the values). Each entry's HMAC covers its own contents and the hash of the entry before it. amagi-crm audit verify exits 1 and names the first bad entry if a row was edited, deleted or reordered:

$ amagi-crm audit verify
Audit chain BROKEN at entry 3: row content does not match its hash (row edited)

amagi-crm audit tail -n 20 shows recent entries. The chain can't reveal entries cut off the end, so copy the head hash somewhere off the server now and then and compare it later.

Limits

  • One firm, one time zone, one shared calendar. No per-user calendars and no recurring appointments.
  • Search decrypts rows in the app, since encrypted columns can't be searched in SQL. That's fine for tens of thousands of contacts, not millions.
  • No email or SMS sending, and no two-way calendar sync (the .ics feed is read-only).
  • Encryption protects the database and its backups. It doesn't help if an attacker can read .env or the memory of the running server.
  • This isn't a compliance certification. It gives you the parts (encryption at rest, an audit trail, roles), and your firm decides whether a deployment meets its rules.

Star this if it saved you time

If amagi-crm replaced a CRM bill you were paying every month, a star helps other small firms find it.

Other AmagiLabs projects

  • amagilabs on GitHub: small open-source tools for small businesses and law firms from AmagiLabs (Amagisolutionsgroup, Inc.).
  • amagi-legal-review-skills: Claude Code skills for a first-pass review of NDAs, MSAs and leases.

License

MIT. See LICENSE. Copyright (c) 2026 Amagisolutionsgroup, Inc. (AmagiLabs).

Metadata

Release files for amagi-crm 0.1.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 amagi-crm 0.1.0
File Size Uploaded Provenance
amagi_crm-0.1.0.tar.gz 104.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for amagi-crm 0.1.0
File Interpreter ABI Platform
amagi_crm-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 197.1 kB

Release files / amagi_crm-0.1.0.tar.gz

Download URL amagi_crm-0.1.0.tar.gz
Size 104.6 kB
Tags Source
SHA-256 checksum
How to use checksums
8b2d179ed08a6bcaa63252fd9af3331cc9c2047ae7dc7367f82e0b0a800bae59
BLAKE2b-256 checksum
How to use checksums
ca1fa25a961d43adad1cb06898ce71713734bc34a3820ec605b9756ca1b340b1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release files / amagi_crm-0.1.0-py3-none-any.whl

Download URL amagi_crm-0.1.0-py3-none-any.whl
Size 92.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
60741da881e4405a781e933603954ca164c95acf96fd3563451c6edbad5ae891
BLAKE2b-256 checksum
How to use checksums
0e86f327149a00bde7222ddf4c77a2efdf7cedeea29709ac7a83aceb4abbe2aa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Security for amagi-crm 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

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