Client jobs, CRM & money
Schedule professional client work, keep every client record, quote and invoice it, and deliver the files.
Client jobs is the professional-work side of BeaconUAV: jobs like mapping, inspection, or content shoots, scheduled for an external client, confirmed through a secure link, and finished by handing over deliverable files. Around it sits the client record itself: contacts and companies, what you quoted them, what they owe, and what they have prepaid.
The flow
- Create the job (squadron/district admin): title, window, client name, optional client email. It starts in Awaiting client.
- Share the client link: copy it from the job's Client link tab. The link is the client's credential: no account, no login.
- The client confirms by opening the link, and the job flips to Confirmed.
- Fly the job. Admins move the status forward: In progress → Delivered. Delivery requires at least one uploaded deliverable. BeaconUAV enforces it.
- The client downloads: per file, or as a single zip. Downloads work only while the job status is exactly Delivered.
Contacts & Companies
Every client is a real record at /crm, not re-typed per job. Step 1
above ("Create the job") now starts with picking an existing contact or
creating one inline. The job snapshots that contact's name and email at
pick time, so editing the contact later never changes what's already on a
job a client has seen. A contact's page lists every job booked for them,
their deliverables, and any flight logs bookkept against those jobs.
Contacts can optionally belong to a company, which rolls up the same
history across everyone at that company.
Managing records is inline: + Contact / + Company buttons on /crm
create records, the pencil on each row (or the Edit button on a detail
page) opens the same dialog, and deletes live there behind a two-click
confirm. Contacts carry free-form tags (repeat, net-30, priority)
for grouping, and the list can be filtered by company, searched across
name/email/tag, and exported to CSV.
Each company can carry an email domain (e.g. cartercivil.example).
When a new contact's email matches one, the dialog offers the company as a
checked suggestion: keep the check to link it, clear it to leave the
contact unlinked. Nothing is ever associated behind your back, and free
mail domains (gmail.com, outlook.com, …) never suggest a company at
all, since matching a client to "Gmail" helps nobody. The company list
shows how many contacts sit under each company.
Keeping the list clean
An email address is the identity key: one contact per address, per org. If
someone else on the team already added pm@ridgeline.example, the create
dialog says who has it and links straight to that record instead of letting
a second one exist. The database enforces the same rule.
Duplicates (top of /crm) flags pairs that share an email, a phone
number, or a name once punctuation and company suffixes are stripped. Open
a pair to see both records side by side: pick which one survives, and for
each field (name, email, phone, company, notes) which value wins. Tags are
combined, and the merged-away record's jobs and activity move to the
survivor before it's deleted. Choose Not a duplicate and that pair stops
being suggested.
Import takes a CSV export from anywhere else. Map its columns (headers like Name, Email, Company are guessed), check the dry run, then import. Rows whose email already exists update that contact rather than creating a second one, and companies are matched to existing records by name rather than invented from a spreadsheet typo.
Activity and follow-ups
A contact or company page carries one merged timeline. Log anything worth remembering: a note, call, email, or meeting, or set a task with a due date. Open tasks sit above the history in an Upcoming block (overdue in red); completing one moves it into the history. Beneath it, the same timeline shows the operational record that already exists on the account: deliverables handed over and flight logs bookkept against the contact's jobs.
Nothing logged here is shown to the client. The timeline is internal. The job list stays its own section, since a job is the linked, navigable record rather than a line in the history.
Views and bulk edits
The filter row above the contacts list doubles as a saved view: search, company, tag, Open tasks, and sort, saved under a name and (optionally) set to open by default. Select rows with the checkboxes to act on many at once: set their company, add or remove a tag, or delete them (which asks twice). Export always writes the view you're currently looking at, including tags, open-task counts, and last-activity dates.
Money
Everything a client owes and everything they have paid lives with the
client, under Money on /crm (or the Billing section of a contact or
company page). The ledger is yours; the documents are the client's, and
they can be handed over as real files or opened on a link. See
Branded documents.
Quotes price the work before you fly it. A quote is drafted, sent, and then marked accepted, declined or expired; once it is accepted, Convert to invoice produces the invoice from it in one step. The lines are copied, not shared, so editing the invoice afterwards can never rewrite the quote the client already agreed to.
Invoices carry their own lines, a tax rate applied to the taxable lines, an optional discount, terms and a due date. Statuses are Draft (not issued), Sent, Part paid, Paid, and Void, plus Overdue, which is worked out from the calendar rather than being a status someone has to remember to set: a sent invoice whose due date has passed with a balance left on it. Issuing an invoice records when it went out; the money state stays separate, so a check that arrives before the document goes out does not leave the invoice claiming to be a draft.
Payments are recorded by hand: amount, date, method (check, transfer, cash, card, other) and a reference. Partial payments accumulate: the balance and status update themselves, and a full payment marks the invoice paid. Reversing a payment is a void with a reason, never a delete, so the ledger keeps its history and the invoice walks back to Part paid.
Prepaid credit covers the retainer case: Add prepaid credit on a client page records money received before any invoice exists, and it sits on the client's account until you apply it to an invoice with the Account credit method. The remaining balance is deposits minus credit drawn down, so it can never drift from the payment history.
Budgets put a cap on a client: per month, quarter, year, or a fixed
ceiling that never resets. The client's page and /crm show how much of the
cap the period has consumed, turning amber at your warning threshold and red
once it is over.
Line items can also carry an internal unit cost, which never appears on anything a client sees. Where costs exist, the invoice shows the cost basis and margin; where they don't, those rows simply don't appear.
Progress invoicing bills a project in draws instead of all at once. From an accepted quote, Convert to invoice offers Everything or A draw: pick the lines and a percentage, and the invoice carries that share, 30% up front, the rest as the work lands. The quote stays open and still billable until its last line is drawn down, and it never counts work twice: asking for more than remains is refused rather than quietly trimmed. Voiding a draw invoice puts its quantity back in the pool, since voiding is how a wrong invoice is undone.
Price book (/crm/billing/price-book) turns line entry into one choice. Each
service carries a name, a unit, the price you charge and the internal cost, so
the picker on every line fills four fields at once and the margin columns stop
being empty. Archiving retires a service without erasing the lines written from
it.
Statements of account are on every contact and company page: one page of every open invoice with a running total, printed or saved as a PDF, built from the same letterhead as the documents themselves.
Reports (/crm/reports) turn all of that into answers instead of a bigger
list. It is built from the same quotes, invoices, payments and jobs you already
record, so nothing has to be tagged or exported to make it work: the quote
funnel and win rate, twelve months of invoiced against collected with what was
owed at each month end, revenue by client with repeat clients marked, how long
clients take to pay, job margin from the internal cost on each line, and who
has gone quiet. The two figures that follow from the ledger rather than from
counting rows are worth stating plainly: a draft is never counted as billed
(it owes nothing yet), and a credit draw settles an invoice without counting
as money in, so the outstanding figure here is the same balance the billing
hub shows you.
A document belongs to one client, and to the job it is filed under. A job that names a contact is that client: a quote or invoice filed under it inherits the contact when you leave it blank, and one naming someone else is refused by the database itself, so no other path into the API can file an invoice against the wrong job. The billing hub therefore groups quotes and invoices under the job they belong to instead of showing one flat pile, with the documents that name no job gathered in their own group, and every client job page has a Billing tab listing that job's paperwork and nothing else.
Branded documents
A quote or an invoice is a real file, not a screenshot of a page. Document
settings (/crm/billing/settings) holds the letterhead once: legal name, tax
ID, address, phone, email, website, logo, brand colour, payment
instructions, an optional online payment link, default terms, a footer note and
your document number prefixes. Every document is rendered with it. Edit
the logo and every future rendering has the new one; nothing is frozen into old
documents.
The preview beside the form is built from the draft you are typing, with the same model and the same renderer the PDF and the printed page use, so what you see while typing is the document, not a mock-up of it. Client records now carry addresses too (a company address wins, the person becomes the attention line), which is what makes a Bill to block possible at all.
On any quote or invoice: Print isolates the document and opens the browser's dialog (the app around it is hidden), and Download PDF builds the file in your browser, logo and all, with no server round trip and no third-party renderer.
Send a link, not an attachment. Share publishes a token URL
(/quote/<token>, /invoice/<token>) that opens the document itself, same
letterhead, same lines and same totals, with no account and no login. A quote
there can be accepted or declined for real: acceptance records the name the
client typed and when, a decline takes a note, and both are facts stored on the
quote rather than an email you have to reconcile. Revoke pulls the link
instantly. Only what belongs on a client document is exposed: the internal unit
cost, your margins and every internal id stay on your side.
Remind composes the chase and queues it against the document (before and after the due date), and the queued email is visible in the activity around the record.
Getting paid
Clients never pay inside BeaconUAV. Nothing here can charge, refund or hold funds. The money goes straight to the account you already use, and BeaconUAV is only told about it:
- You record it. Amount, date, method, reference by hand, as above. This is all you need.
- Your processor tells us (read-only). On Document settings, create the
Payment sync endpoint and paste that URL into Stripe / Square / PayPal as a
webhook, or point Zapier at it. Arrivals land in the Payments inbox
(
/crm/billing/payments), matched to an invoice where the match is obvious and waiting for one click where it is not. Confirming writes an ordinary payment, which is what actually settles the invoice. - You paste a settlement export. Record an arrival in the inbox takes what your statement says; it lands in the same place, with the same review.
The endpoint token is generated in your browser and only its SHA-256 is stored, so it is shown exactly once and a database leak cannot be replayed against your processor. Rotate is the revoke button.
Arrivals are also signed, with secrets that belong to your organization alone. BeaconUAV verifies the signature over the exact request body inside a five minute replay window:
- Stripe:
Stripe-Signature, checked against your webhook signing secret (whsec_…). Paste it under Signature verification on the Payment sync card. - Square:
x-square-hmacsha256-signature, checked against your webhook signature key, pasted in the same place. - Bank, CSV and Zapier forwards:
x-sticktime-signature(t=<unix>,v1=<hex HMAC-SHA256 of "t.body">) with the relay signing key that is shown once when you create or rotate the endpoint.
Secrets are write-only: the settings page shows only whether each one is configured. A Stripe or Square secret can be cleared on its own (for a retired or leaked key); revoking the endpoint deletes them all. Until a signature matches, an arrival is recorded as unverified: it appears in the inbox for a human to review, it can never be automatically applied to an invoice, and it is never counted as settled money. Automatic matching is off by default, so even a verified arrival waits for a click unless you turn it on. Nothing here can move money in either direction.
Security model
- The link is single-use per job and shown once; Rotate link kills the old link instantly (for a client who forwarded it too far).
- Every client action, from confirming the job to downloading files, is re-verified against the secure link on BeaconUAV's servers rather than trusted in the app.
- Deliverable files are stored privately and are only reachable through the job's secure link. They can't be guessed, shared out of context, or discovered by anyone else.
- Clients see only the details meant for them, never your internal records, and never the link itself.
- A shared document exposes an explicit allowlist of fields: no cost basis, no margins, no organization/contact/company ids, no line provenance.
- A client can accept or decline their own quote and do nothing else; every other transition (issuing, voiding, recording money) is yours, and a replayed request changes nothing.
- The payment-sync endpoint stores only the hash of its token, and can read and write nothing but the arrival it is told about. BeaconUAV never holds a credential that can charge or refund anyone.
Client email boundary
Job emails send themselves. When a job is confirmed or marked delivered
and it has a client email, BeaconUAV emails the client a link to the job page.
By default the email comes from your-org@ BeaconUAV's mail subdomain, with
"your from name via BeaconUAV" as the sender and your reply-to as the reply
address, so client replies reach you. The address is set once from your
organization's name and does not change if you rename it. A
name that imitates a well-known brand, or
uses characters outside plain Latin letters, is replaced with a neutral sender. Test addresses (example.com, .test and
similar) are never mailed, and an email that could not go out within a day is
dropped rather than sent late.
Use your own mail server, and your own domain. In the same panel you can point BeaconUAV at your organization's SMTP server. It is only used after you send a test message that actually goes through it — the connection, the encryption and the password are all checked, the test is addressed to your own account rather than a client's, and it is sent from your own From address so it also proves the server will accept you as a sender.
Once that test passes, your automatic email goes out as you: from the From address you set, on your own domain, through your own server. Your existing SPF and DKIM records apply, the sender name is just your organization rather than "your organization via BeaconUAV", and a client who has never heard of us is receiving mail that verifies. Invitations and removals go the same way.
Until that test passes, and whenever the server later stops working, automatic job email leaves from BeaconUAV's subdomain instead, so a client never misses a booking link because a mail server was misconfigured or decommissioned. The From address always changes with the transport — a message claiming your domain but signed by our provider would fail SPF/DKIM alignment and be treated as spam, so the two are never mixed.
CRM → Billing → Document settings shows your address, an Automatic job emails switch to pause them, and a log of every email to your clients: sent, not sent and why, or failed.
Quotes, invoices and reminders are never sent on their own: you still review and send those yourself from the prepared draft. Organization invitations and offboarding notices are different — they are transactional mail whose only purpose is to arrive, so those go out automatically.
Where things live
| Piece | Location |
|---|---|
| Internal board | The Client jobs page (squadron/district admins) |
| Public portal | The job's secure client link |
| Deliverables | Attached to the job; download as file or zip |
| Clients, quotes, invoices, budgets | CRM at /crm; billing at /crm/billing |
| Letterhead, logo, pay instructions, sync endpoint | Client billing → Documents (/crm/billing/settings) |
| Services and prices | Client billing → Price book (/crm/billing/price-book) |
| Arrivals from your processor | Client billing → Payments (/crm/billing/payments), with the waiting count on the badge |
| What it all adds up to | Client billing → Reports (/crm/reports) |
| Client's copy of a quote or invoice | Its own token link (/quote/<token>, /invoice/<token>) |