Built by Marketlinx DIGITAL for C3 Consulting
Donor records, event imports, IRS-compliant tax receipts and a self-serve donor portal, synced to the nonprofit's HighLevel account. Built on one rule: nothing writes to the database until a human has reviewed it and signed.
Running in production for C3 Consulting. Screens below are recreated from the live product.
Nonprofits run on event exports from platforms like Greater Giving and OneCause, cheques in the post, and volunteers doing data entry. The commercial donor platforms price small organisations out, and generic CRMs don't understand tax deductibility, fair market value or receipt law.
The DMS was built around that reality. It imports what nonprofits actually have, catches the duplicates a spreadsheet never would, produces receipts the IRS will accept, and keeps the HighLevel account in step so communication runs from live giving data.
The system at a glance
The admin home
Every number on the dashboard reads from precomputed aggregates, so it loads in under a second and is never stale by more than one transaction. The header always names the connected HighLevel sub-account, so an admin can't bulk-operate on the wrong nonprofit by accident.
Dashboard · Example Nonprofit
Connected to GoHighLevel sub-account: Example Nonprofit (location-id)
Donors
73
Total raised (YTD)
$73,125
Events
9
Avg gift
$926
| Event | Date | Raised | Donors |
|---|---|---|---|
| Annual Gala 2025 | 2025-04-29 | $73,125 | 73 |
| Spring Lunch 2026 | 2026-04-10 | $700 | 2 |
| Anniversary Gift 2026 | 2026-04-01 | $1,000 | 1 |
Also on the real screen: three years of year-on-year giving, totals by transaction category, and donor tiers at $100 / $1k / $5k / $25k of lifetime giving.
Uploading data
Whether an admin types in a single cheque or drops a 2,000-row event export, the data goes through the same pipeline. The single-entry form is literally wrapped into a one-row CSV behind the scenes, so duplicates are caught either way.
STAGE 1
Headers normalised, money parsed, three date formats accepted, multi-date rows split. Banner rows from Greater Giving auto-skipped.
STAGE 2
Required columns checked, transaction types validated, and every row must carry an identity: an email, a full name or a business name.
STAGE 3
Rows inside the same file sharing a fingerprint of donor, event, type and amount are flagged as a group.
STAGE 4
Rows matching a transaction already in the database are pre-flagged, so last month's import can't be counted twice.
STAGE 5
Every row, flag and match candidate shown to the admin, who signs before anything writes.
Header mapping
Greater Giving, OneCause and BidPal all name their columns differently. Built-in presets translate the common exports automatically; anything else is mapped once in a dialog. One column can feed several fields (a single donor name split into first and last), and several columns can feed one (item name and detail concatenated into a description).
The confirmation screen
Nothing writes until the admin ticks the authorisation, types a signature and clicks Confirm. Fuzzy matches above 85 per cent confidence default to merging with the existing donor; between 65 and 85 the system asks; duplicates from earlier imports default to being skipped. Every default can be overridden per row.
Confirm Import · example-event-2025.csv · Batch b398f5db
Valid rows
63
Row errors
2
To merge
11
To create
48
To drop
4
4 rows match existing transactions from a previous import. They are pre-selected to drop so nothing double-counts.
| Row | Donor | Event | Paid | Decision |
|---|---|---|---|---|
| 2 | John Smith john.smith@example.com | Annual Gala 2025 | $500.00 | Create new donor |
| 3 | Jane Smith ⚠ FMV ($800) exceeds amount paid ($750) | Annual Gala 2025 | $750.00 | Merge into Jane Smith (92%) |
| 4 | Acme Corporation Already imported 04/05/2026 | Annual Gala 2025 | $5,000.00 | Skip (already imported) |
☑ I confirm this data is accurate and I have authority to submit it. Signature: A. Admin
Confirm and importFlags under each donor catch what spreadsheets miss: fair market value exceeding the amount paid, organisation names typed into person fields, zero-amount pledges, unpaid transactions, donors with no email who can't reach the portal.
Wrong fuzzy match? Two John Smiths at the same nonprofit can match each other at 100 per cent. One click on the decision dropdown keeps their giving records separate.
On confirm, transactions write, donor aggregates recompute, and the HighLevel sync fires. Rejecting the batch discards every staged row and writes nothing.
Receipts
One click generates a draft receipt for every donor with tax-relevant giving in a chosen year. Each is a three-section PDF: donations are fully deductible, auction wins and event tickets deduct the amount paid minus fair market value, and raffle tickets are excluded entirely, because the IRS says so.
Finalised receipts lock at the database level with no exceptions. A correction never edits the original: regenerating issues a new receipt with a supersede pointer back to the old one, and both stay in the audit trail. Donors download their own from the portal on signed links that expire after an hour.
The donor portal
Donors sign in with a six-digit one-time code sent through the nonprofit's own HighLevel channel. They see lifetime and year-to-date giving, every transaction with its deductible value, and a download button for each finalised receipt.
Made a gift that isn't showing? They submit it from the portal: date, amount, description. It lands in the admin's review queue as pending, and nothing becomes a real transaction until an administrator checks it against the bank and approves. Rejections carry a note the donor can see, and either outcome shows in their portal.
HighLevel sync
Synced
30
Failed
0
Last attempt
2026-05-28 10:18
| When | Donor | Status | Detail |
|---|---|---|---|
| 10:18:49 | Acme Corporation | synced | hjNo5Yf3GxqTfFCh3xmH |
| 10:18:48 | John Smith | synced | snk1NhXHawVF93PeYus8 |
| 10:18:47 | Jane Doe | synced | ZEaFdrWerDeWkFMHtxj6 |
Donor aggregates push one way into custom fields on the HighLevel contact, so campaigns, workflows and segments always run from live giving data. Matching is on email, updates are idempotent, and a manual "Sync this donor" button exists for the impatient.
The log keeps the last 200 attempts with the exact error on any failure: an expired token says so, a deleted custom field says so, and rate limits retry automatically with backoff. This is the reversible, logged, no-mystery approach I put into every build, applied to a whole product.
The DMS runs in production today. The same architecture, staged writes, full logging and a HighLevel sync, applies to any industry where the data has to be right.
Free account audit
Free account audit
Two minutes now, a written answer inside two working days. Anything urgent goes to the front, so say so in the form.