groveflow admin

The owner's cockpit. Enter the admin key to continue.

groveflow.org/admin · keys are kept for this tab only
groveflow
Owner dashboard
groveflow.org · admin

Overview

The whole business, at a glance.

Customers

Everyone with a Groveflow workspace.

Invoices

Raise an invoice, watch it get paid.

New invoice

The server prices it; this preview uses the same rules.
Bill to
Plan & period

Total preview

Core $5 · min $99  ·  Growth $9 · min $149  ·  Pro $14 · min $299 — per seat, per month. Total is whichever is greater: seats × price, or the plan minimum.

Issued invoices

Billing

Self-serve Stripe subscriptions — keys, plan prices, and whether the next click moves real money.

Stripe & billing

Write-only — nothing here is read back

What is configured

Subscriptions

Counts only, never an org or a Stripe id

Email

SMTP, templates, and everything Groveflow has sent.

SMTP settings

Template previews

Rendered by the live server

Send log

Outreach

Cold email to businesses that have never heard of Groveflow — on its own transport, with the rules enforced rather than remembered.

Import a list

The preview writes nothing

Pick a CSV, preview it, then import. The preview does the whole job and writes nothing: it shows the column mapping it worked out, exactly which rows would be created, and every row it would reject with the row number, the column as your file spells it, and why. A list is imported once — finding out the mapping was wrong afterwards means a deduplicated mess.

Values for the whole file, where a column is missing

Prospects

Prospect records are personal data: each one is sealed with server/crypto-at-rest.js before it touches the database, carries a retention date, and Delete erases it. The suppression entry, if there is one, deliberately survives the delete — otherwise the next import quietly puts somebody back on a list they asked to leave. Tags come from the import file; there is no tag editor here because server/outreach.js exposes no writer for one, and a button that pretended otherwise would be a lie.

The fourteen

Template

Live preview

Merged against a real prospect

This is the merge the send would do, refused for the same reasons a send would be refused. An empty merge field is a refusal, not a blank: a half-merged mail-merge is worse than no email at all.

What stops a sequence

All four are automatic
  • A reply. There is no IMAP poller and this console does not pretend there is one: outreach carries a Reply-To you watch, and marking the reply on the Campaigns tab is what stops the sequence.
  • An unsubscribe. Honoured immediately — the moment the one-click POST lands, not in ten days.
  • A bounce. A hard bounce suppresses the address at once; soft bounces retry a bounded number of times and then stop.
  • Becoming a customer. Nobody gets step three of a cold pitch for the product they now pay for.

Every step is also re-checked against the suppression list at the moment it sends, never at the moment it was queued. A list built an hour ago is stale.

Sequences

New sequence

Steps

Enrol a prospect

Queue

Per-message log

Queued, sent, refused, bounced, unsubscribed, replied — with the error text

Record what came back

There is no IMAP poller — this is the honest substitute

Bounces and complaints that arrive at the Reply-To mailbox, and replies from real people, are recorded here. A hard bounce and a complaint suppress the address immediately; a reply or a signup stops the sequence.

There is no way to send to these addresses

Deliberate
  • Checked at send time, on every single message. Not at queue time — a list built an hour ago is stale, and the person who unsubscribed in between is exactly the person a queue-time check misses.
  • No override exists. There is no "send anyway", no admin flag, and no route on this server that takes one. If that ever seems necessary, the answer is no.
  • An unsubscribe is honoured immediately. CAN-SPAM allows ten business days; ten days of mail to somebody who asked you to stop is how a sender earns a complaint instead.
  • Every Groveflow customer is on it, live. Queried from the accounts table at send time, so somebody who signed up thirty seconds ago is already excluded. Never cold-pitch someone who pays you.
  • Addresses are not shown. The list stores a keyed hash, and this screen renders the first sixteen characters of it. A list of people who told us to go away does not need to be readable to work.

Suppressed

Add one by hand

Somebody asked to be left alone by reply, by phone, or through a form. Adding them here is permanent: nothing on this screen removes an entry, and that is the point.

Why outreach has its own transport

The single biggest risk in this feature

Password resets, workspace invitations and signing links go out over the same Postfix, the same IP and the same domain as everything else Groveflow sends. If cold email earns spam complaints, the reputation damage lands on the mail paying customers depend on — a signing link that never arrives is a lost deal, and a reset code that never arrives is a support ticket. Neither failure announces itself.

  • Outreach gets its own relay and its own From domain, configured separately from the transactional SMTP settings — the outreach.* credentials, whose environment names are GF_OUTREACH_SMTP_HOST, GF_OUTREACH_FROM and the rest of that family. Groveflow compares the two and refuses to send when they match. It does not fall back to the transactional relay when the outreach one is unset; that fallback is the exact thing this feature exists to prevent.
  • On a single VPS the IP is shared no matter what. A subdomain partitions domain reputation and DMARC; it does not partition an IP, and receivers — Gmail especially — score both. A dedicated relay with its own egress is the only real separation, which is why the relay is configurable and why this console says so instead of showing a green tick.
  • Outreach cannot delay transactional mail. The queue, the caps, the warm-up ramp and the jitter live in the outreach transport with its own connection pool. A throttle or a failure here never touches the path a password reset takes.

The compliance position

Enforced, not documented
  • No postal address, or no unsubscribe mechanism, means nothing sends. A refusal, not a warning. A message sent without a real physical address is already the CAN-SPAM violation, so warning and sending anyway would move the decision to whoever is least likely to read the warning.
  • Every message carries List-Unsubscribe and List-Unsubscribe-Post (RFC 8058) so Gmail and Outlook show an Unsubscribe button, plus a visible link and the postal address in the body. Without the https endpoint there is no button, and the recipient who wants out presses Spam instead.
  • Canada is blocked without consent. CASL requires consent before the first commercial message, company or not.
  • Sole traders and partnerships are blocked without consent, anywhere. They are individual subscribers in UK and EU law however the address is spelled. A corporate subscriber may be emailed under legitimate interest; an individual may not.
  • An unfilled merge field is a refusal. Both before and after the merge runs.

Open and click tracking

Both off, and neither is built
Open tracking: off Click tracking: off
  • Open tracking is a 1×1 image with the recipient's identifier in its URL. It records that a named person opened a message, at what time, from roughly where — personal data processing under GDPR — and remote images are one of the signals filters weigh against a sender.
  • Click tracking rewrites every link so it passes through this server first. It records which links a named person clicked, and it makes the visible destination differ from the real one, which is what phishing does and what filters look for.
  • Neither exists in this system. If either is ever added it must be opt-in per campaign and disclosed in the privacy policy before the first message goes out — with what is collected, why, and for how long. Turning it on quietly is the part that is unlawful.

DNS pre-flight

SPF · DKIM · DMARC · PTR — resolved live

This resolves the real records. It is the check to run before the first send: the PTR record on this box does not match the hostname Postfix says in EHLO, forward-confirmed reverse DNS therefore fails, and Microsoft 365 and Outlook penalise exactly that. Sending before it is fixed wastes the list at the receiver that weighs it most heavily.

Credentials

Every third-party secret Groveflow holds, in one place — no SSH session needed.

System

Health, hosting, and the runbook.

API health

Where the data lives

Workspace data is client-side in this demo. Each visitor's HR workspace (people, time off, payroll runs) lives in their own browser via the app's seed + localStorage — the VPS only holds accounts, invoices, and the email pipeline. Nothing on this dashboard can see or reset a visitor's demo data.

Serving admin.groveflow.org

DNS + certificate runbook
  1. Add the DNS record at the registrar — an A record pointing the subdomain at the VPS:
    admin.groveflow.org. A 2.25.105.89
  2. Wait for it to resolve (usually minutes): dig +short admin.groveflow.org should print 2.25.105.89.
  3. Expand the certificate to cover the new name, then reload nginx:
    # on the VPS sudo certbot --nginx --expand -d groveflow.org -d www.groveflow.org -d admin.groveflow.org sudo systemctl reload nginx
  4. Until then, this dashboard is also served at groveflow.org/admin/ — same file, same API.