Churchtools-invite is a lightweight Go CLI for mass ChurchTools system invitations from a CSV file, including:
- CSV import with person IDs and optional e-mail sync
- REST API invitations (
POST /persons/{id}/invite) - Person export with campus, invitation and group filters; by default only persons not yet invited
- Setup, dry-run and permission helpers
- Skip already invited persons by default; if the CSV e-mail differs, update
the address and invite again (
--reinvitere-sends to persons with status Eingeladen only; Registriert is always skipped)
- Read person IDs from CSV (
id,person_id,ct_id, …) - Send invitation e-mails via the ChurchTools REST API
- Export persons (campus, invitation status, group; interactive selection)
- Setup commands for instance name, login token, connection test and permission hints
- Dry-run mode to check CSV and person data before sending
- Sync CSV e-mail to ChurchTools when it differs (old address kept as additional)
- Skip already invited persons when the e-mail matches; if the CSV e-mail
differs, update and invite again;
--reinvitere-sends to Eingeladen only (persons with status Registriert are never invited again) - Automatic group membership request when export or e-mail sync permissions are missing
- Go 1.26+ (for building from source)
- ChurchTools account with permission Invite persons to ChurchTools
- For export: export data permission (group “Personen exportieren”)
- For e-mail sync on invite: write access permission (group “Personen bearbeiten”)
- Login token or username/password
Pre-built binaries for Linux, macOS, and Windows: Releases
Extract the archive and run churchtools-invite (churchtools-invite.exe on
Windows).
go install github.com/janmz/churchtools-invite@latestgit clone https://github.com/janmz/ChurchToolsInvite.git
cd ChurchToolsInvite
go build -o churchtools-invite .On Windows the executable is churchtools-invite.exe.
cp config.example.json config.json
# edit config.json or run setup init
./churchtools-invite setup test
./churchtools-invite export -o personen.csvEdit the list manually, then dry-run:
./churchtools-invite invite -f personen.csv --dry-run
./churchtools-invite invite -f personen.csvGlobal option: -c config.json for an alternate config path.
Copy config.example.json to config.json or use environment variables:
| Variable | Description |
|---|---|
CT_BASE_URL |
Instance name (e.g. meine-gemeinde) or full URL (https:// only; http:// only for localhost) |
main_url / CT_MAIN_URL |
Main (central) instance for a sub-instance login; only used when set explicitly |
CT_LOGIN_TOKEN |
API login token |
CT_USERNAME / CT_PASSWORD |
Alternative to token |
delay_ms |
Pause after each invitation or e-mail update in milliseconds, 0–60000 (default: 500; skipped rows cause no pause) |
campus_id |
Default campus when the user has none (set interactively on first export) |
pre_join_groups |
Comma-separated groups to join in order before export/invite (default in config.example.json; set - to disable) |
CT_PRE_JOIN_GROUPS |
Environment override for pre_join_groups |
Obtain a login token:
./churchtools-invite setup init
# or, after login:
./churchtools-invite setup tokenIn multi-campus ChurchTools setups, a sub-instance URL may look like
https://main-sub.church.tools (e.g. https://meine-gemeinde-unterstadt.church.tools).
User accounts live on the central instance
https://main.church.tools (e.g. https://meine-gemeinde.church.tools).
The central instance is used only when it is configured as main_url
(or CT_MAIN_URL). setup init suggests it for names like church-site
and stores it only after confirmation – a host derived from the name could
belong to another church, so credentials are never sent to a guessed host.
If direct login on the sub-instance fails and main_url is set,
username/password auth runs the OAuth bridge (when direct login succeeds,
OAuth is skipped):
- Login on the central instance (
/api/login) oauthclients/…/startloginon the sub-instance (redirect to central)- OAuth authorize using the central session
- Callback on the sub-instance → local session
- API calls keep using the sub-instance (configured URL)
setup init can then fetch a sub-instance login token automatically
(/api/person/me/apitoken).
If a login token is only valid on the central instance (and main_url
is set), the tool falls back to the central URL for API calls (note in output).
Check permissions:
./churchtools-invite setup permissionsBy default, only persons who have not been invited yet are exported. Use
-i or --invited to include already invited or registered persons as well.
id,vorname,nachname,email,standort,status
123,Max,Muster,max@example.org,Rhein-Main,NEU
456,Anna,Beispiel,anna@example.org,Rhein-Main,Eingeladen
789,Tim,Test,tim@example.org,Rhein-Main,Registriertstatuscolumn:NEU(not yet invited),Eingeladen(invitation pending) orRegistriert(ChurchTools account already accepted)standortcolumn: campus name from ChurchTools (export only)- Columns
id,vorname,nachname,emailare the invite CSV;standortandstatusare for manual review and are ignored on import withinvite
Campus: --campus is only required when you do not want to export
for your own campus, or when your user account has no campus assigned.
Without --campus, export automatically uses the logged-in user's campus or
campus_id from config.json.
--campus accepts either a numeric campus ID or a unique name substring. For
name lookup, the campus list and search string are lowercased; everything
except a–z is stripped from the search string. The tool picks the campus
whose name contains the cleaned substring – there must be exactly one
match (contains(campus.name, searchstring)).
Examples:
# Own campus – no --campus needed
./churchtools-invite export -o personen.csv
# Other campus by ID or unique name fragment
./churchtools-invite export -o all.csv --campus 3
./churchtools-invite export -o rheinmain.csv --campus rhein
# Include already invited/registered persons
./churchtools-invite export -o review.csv --invitedid,vorname,nachname,email
123,Max,Muster,max@example.orgidcolumn is required (also:person_id,ct_id)- Name and e-mail columns are optional; e-mail is used to update ChurchTools before inviting when it differs from the stored address
- Extra columns such as
standortandstatusfrom export are ignored
invite --dry-run runs the same checks as a real invite but does not send
invitations and does not change any person (no e-mail sync). Pre-join and
permission groups are still joined, because reading persons needs those
rights; like in a real run they are left again at the end if you were not a
member before. For each CSV row it verifies:
- Does the person ID exist in ChurchTools?
- Is the person already invited? Detected e.g. via
invitationStatus(accepted,pending). By default: skip if the CSV e-mail matches ChurchTools; if it differs, simulate e-mail update and re-invite. Registriert persons are always skipped;--reinviteapplies only to Eingeladen - Is there an invitation e-mail? ChurchTools always invites to the person's
ChurchTools address; with
--no-sync-emaila person without one fails. - Would an e-mail sync from the CSV be required?
Output: each row is printed as soon as it is processed (OK, ÜBERSPRUNGEN
or FEHLER, German labels), followed by a summary that repeats the failed
rows. Exit code 1 if at least one row failed. Ctrl+C stops after the
current row, prints the summary and leaves the temporary groups; a second
Ctrl+C terminates immediately.
Recommended before the first real run. All invite options (--reinvite,
--no-sync-email, …) apply to dry-run as well.
| Command | Purpose |
|---|---|
setup init |
Interactive config.json (instance name, optional main instance, hidden password/token input) |
setup test |
Test login and connection |
setup token |
Fetch your own login token and store it encrypted in config.json (never printed) |
setup permissions |
List invite-related permissions |
whoami |
Show logged-in user, campus, group memberships (sorted by name, format Name ID) and instance URL |
export -o FILE |
Export persons to invite CSV format (default personen.csv; - = stdout, hints go to stderr); by default only not-yet-invited persons |
export --force |
Overwrite an existing output file (otherwise the export stops) |
export -i / --invited |
Export all invitation statuses (NEU, Eingeladen, Registriert) |
export --interactive |
Choose campus, optional filters (person status, group) and invitation status ([n] Neu, [e] Eingeladen, [r] Registriert) |
export --campus VALUE |
Campus by ID or unique name substring (see CSV format); only needed for a different campus or when the user has no campus |
export --campus all |
All campuses (alias for --all-campuses) |
export --all-campuses |
No campus filter |
export --status VALUE |
Only persons with this person status (member, guest, …) – ID or name, e.g. --status Mitglied; old name --status-id still works. Not the invitation status of the CSV column status |
export --group VALUE |
Only members of this group – ID or name (unique part is enough); old name --group-id still works |
list statuses |
Person statuses of your instance with ID and name (each church defines its own, IDs differ per instance) |
list groups [--campus X] [--search TEXT] |
Groups with ID and name |
list campuses |
Campuses with ID and name |
export --skip-permission-request |
Do not request group membership for missing export rights |
export --skip-pre-join-groups |
Skip pre-join groups before export |
invite -f FILE |
Send invitations |
invite -f FILE --dry-run |
Check/simulate without sending (see above) |
invite -f FILE --delay-ms MS |
Pause after each invitation/e-mail update, 0–60000 (0 = delay_ms from config) |
invite -f FILE --no-sync-email |
Skip CSV e-mail sync (mismatched e-mail → error) |
invite -f FILE --reinvite |
Re-invite persons with status Eingeladen; Registriert is always skipped |
invite -f FILE --skip-permission-request |
Do not request group membership for missing rights |
invite -f FILE --skip-pre-join-groups |
Skip pre-join groups before invite |
invite -f FILE --yes / -y |
Run the plan without asking (scripts); not allowed when the API runs on the main instance |
invite -f FILE --ignore-name-mismatch |
Also process rows whose CSV name differs from the ChurchTools person |
invite -f FILE --allow-single-email |
If ChurchTools rejects the address list, replace the primary e-mail (the previous address is lost) |
Linux/macOS release binaries have no embedded file icon (not standard for CLI
tools). Windows release builds embed project-root vaya.ico via
go-winres.
Tests: Running only go test in the repo root does nothing useful (package main has no tests). Use go test ./... — see TESTING.md for
coverage, conventions, and manual checks against a real ChurchTools instance.
scripts/ci.sh # or scripts/ci.ps1: gofmt, go vet, staticcheck, tests, build
go test ./...
go build -o churchtools-invite .staticcheck (v0.8.2) needs Go 1.26 or newer; on older toolchains set
SKIP_STATICCHECK=1.
- Encrypted secrets:
login_tokenandpasswordinconfig.jsonare encrypted with sconfig on first start (login_token_secure,secure_password; the plaintext field then shows a marker). The encryption is bound to this computer – on another PC enter the secrets again. To change a secret, write the new plaintext value intologin_token/password. config.jsonmust be located below the program directory, the current working directory or your home directory (sconfig rule).config.jsonand exported CSV files are readable only by the current user (Windows ACL,0600elsewhere). Environment variables (CT_*) are never written to the file.- No fallback to guessed hosts: credentials are only sent to
base_urland, if configured,main_url. Redirects to another host or scheme are refused, also in the OAuth bridge (only base and main instance, https).setup initsuggests the main instance the sub-instance itself reports; a host derived from the name has to be typed in. A login-token fallback to the main instance only happens on HTTP 401, andinvitethen warns and requires interactive confirmation. - Temporary groups: groups from
pre_join_groupsand permission groups that the tool joins for a run are left again at the end ofexport/invite; memberships you had before stay untouched. Joined groups are recorded in.churchtools-invite-groups.jsonnext toconfig.json; if a run ends without leaving them (crash, closed window, second Ctrl+C), the next start offers to leave them. Joining via the public group signup form is only done after confirmation. setup tokenstores only your own token, encrypted; it is never printed.- The CSV contains personal data: delete it after the invitations (the tool
reminds you). Cells starting with
= + - @are prefixed with'against formula injection;inviteremoves the prefix again. A person ID listed twice or an invalid e-mail address (exactly one plain address per cell) is rejected before anything is sent. CSV files are restricted to the current user when they are read. - Plan and confirmation: before a real
inviterun the CSV is checked like a dry-run; the plan lists the number of invitations and every e-mail change (old → new) and asks[j/N](--yesfor scripts). Rows whose CSV name does not match the ChurchTools person (shifted row, wrong ID) fail unless--ignore-name-mismatchis given, because the account activation would otherwise go to someone else. - Result lines of a real run mask e-mail addresses (
ma***@example.org); full addresses only appear in the dry-run and in the plan. Text from the server or the CSV is printed without control characters. - Release archives come with a
SHA256SUMSfile (sha256sum -c SHA256SUMS).
Contributions are welcome! Please check CONTRIBUTING.md before creating a pull request.
This software is under a modified MIT license (see LICENSE).
You may freely use, modify, and distribute the code, provided you credit the
original author Jan Neuhaus, VAYA Consulting and maintain a link to the
original repository: https://github.com/janmz/ChurchToolsInvite.
No warranty is provided.
If you find this project helpful, please support CFI-Kinderhilfe: https://cfi-kinderhilfe.de/jetzt-spenden?q=VAYACTINVITE (Donations go to CFI-Kinderhilfe, not the author.)
Author: Jan Neuhaus, VAYA Consulting – VAYA Consulting Repository: https://github.com/janmz/ChurchToolsInvite
See Changelog.md for release history.