7.3 KiB
Email link delivery
The check-in form is reached via a personalized URL that carries the contact's CiviCRM ID + a server-issued checksum:
https://check-in.fci.coop/?cid=<contact_id>&cs=<checksum>
This document covers the three pieces needed to actually get those links into form-fillers' hands:
- A CiviCRM message template that bakes the URL into an email body.
- A way to trigger sending (one of three options below).
- A link-preview endpoint in this app for ad-hoc / testing use.
You don't need all three — most teams use #1 + the simplest version of #2. The preview endpoint (#3) is a debugging convenience.
1. Create the CiviCRM message template
In CiviCRM:
-
Navigate to Mailings → Message Templates → Add Message Template.
-
Title:
Co-op Check-in invitation -
Subject:
Time for your co-op check-in -
Plain-text body (substitute your real domain):
Hello {contact.display_name}, Please take a few minutes to update us on your co-op's progress this month. The form pre-fills your prior responses — you only need to update what's changed. Open your check-in: https://check-in.fci.coop/?cid={contact.contact_id}&cs={contact.checksum} The link is personalized to you and expires in 14 days. If you no longer have it, reply to this email and we'll send a fresh one. With gratitude, The Food Co-op Initiative team -
HTML body (same content, slightly nicer):
<p>Hello {contact.display_name},</p> <p>Please take a few minutes to update us on your co-op's progress this month. The form pre-fills your prior responses — you only need to update what's changed.</p> <p style="margin: 24px 0;"> <a href="https://check-in.fci.coop/?cid={contact.contact_id}&cs={contact.checksum}" style="display: inline-block; background: #3a5520; color: #fafaf7; padding: 10px 20px; border-radius: 6px; text-decoration: none; font-weight: 500;"> Open your check-in </a> </p> <p style="font-size: 13px; color: #5b574b;">The link is personalized to you and expires in 14 days. If you no longer have it, reply to this email and we'll send a fresh one.</p> <p>With gratitude,<br />The Food Co-op Initiative team</p> -
Save.
Token reference:
{contact.display_name}— the form-filler's name{contact.contact_id}— their CiviCRM ID (becomescid){contact.checksum}— a fresh secure token (becomescs)All three are stock CiviCRM tokens. The checksum is generated at send time and includes the recipient's contact-specific hash, so it's both per-contact and per-send.
2. Trigger sending — three options
Option A — Manual: stock CiviCRM Send Email (simplest)
Best for: low volume, one-at-a-time, or you want to review each send.
- Open the organization's contact record in CiviCRM.
- Relationships tab → find the row with "Primary Contact" → click the related individual's name.
- On the individual's contact view: Actions → Send Email.
- Use Template: select
Co-op Check-in invitation. The subject and body populate; tokens render in the preview. - Send. CiviCRM logs the email as an Activity on the contact.
Repeat per contact. Slow but bulletproof.
Option B — Bulk: CiviMail (campaign-style send)
Best for: send the check-in invitation to every active co-op at once (e.g. monthly).
-
Build a Smart Group of contacts whose Primary Contact relationships point at orgs in active stages. Example query:
- Contacts → Advanced Search
- Add criteria:
Has relationship→Primary Contact→ status: Active - Optionally constrain by the related org's
Food_Co_op_Organizing.Stagefield - Save as Smart Group: "Active co-op primary contacts"
-
Mailings → New Mailing.
-
Recipients: the smart group above.
-
Choose template:
Co-op Check-in invitation. -
Schedule send.
Each recipient gets their own checksum embedded in the URL — CiviMail expands the tokens per-recipient.
Option C — One-click: CiviRules action (most polished)
Best for: a button-on-the-org-record workflow.
Setup:
- Administer → CiviRules → Manage Rules → New Rule.
- Title:
Send Co-op Check-in invitation. - Trigger: pick a "manually trigger from org row" action if your CiviRules version supports it. Otherwise: trigger on org Stage change (auto-fires when staff advance an org).
- Action:
Send email to contact via message template.- Template:
Co-op Check-in invitation. - Recipient: Contact in relationship → "Primary Contact" → side B (the Individual).
- Template:
- Save.
When staff change Food_Co_op_Organizing.Stage on an org, the rule fires
and emails the org's Primary Contact automatically — no extra clicks.
Combination
You can use any combination. Common pattern: Option C for stage transitions (automatic) + Option A for ad-hoc resends.
3. The /api/preview-link endpoint (admin debugging)
Generates a working URL for any contact, returning the URL and the contact/org context for verification.
Auth: requires the HEALTH_TOKEN env var (same secret as
/api/health). In development without HEALTH_TOKEN set, no auth required.
Request:
curl "https://check-in.fci.coop/api/preview-link?cid=513&token=$HEALTH_TOKEN"
Response:
{
"contactId": 513,
"contactName": "Bob Sample",
"orgId": 9609,
"orgName": "A Sample Food Co-op",
"orgStage": "Organizing",
"url": "https://check-in.fci.coop/?cid=513&cs=8d1f...",
"checksum": "8d1f...",
"ttlHours": 336
}
Use cases:
- Testing: confirm a contact's setup before sending the real CiviCRM email.
- Manual / external sends: copy the URL into Slack, ad-hoc email, an SMS, etc.
- Debugging: if a contact reports the form not working, generate a fresh URL and try it yourself.
- Bulk export: script-loop over a list of cids to produce a CSV of
(name, org, url)for an external mail-merge.
Errors you may see:
404 — has no active Primary Contact relationship→ the contact isn't linked to any org.409 — multiple active relationships→ resolve in CiviCRM first; the form needs exactly one.501 — Stub mode active→ you forgot to setCIVI_BASE_URLetc.
Operational runbook
"We need to send invitations for this month"
- (Bulk path) Use Option B: build/refresh the Smart Group, schedule the CiviMail mailing, send.
- (Per-org path) Use Option A: open each org → Send Email to Primary Contact.
- (Auto path) Use Option C: just advance stages; emails go out automatically.
"Contact reports the link doesn't work"
- Hit
/api/preview-link?cid=<their-cid>&token=<HEALTH_TOKEN>. - Verify the response shows the right org name + stage.
- Send the URL from the response directly to the contact (or have them try it).
- If
/api/preview-link404s, the contact lacks a Primary Contact relationship — fix in CiviCRM first. - If their old URL truly expired (older than 14 days / your
cs_offsetsetting), CiviCRM rejects it. The new one frompreview-linkwill work.
"We want to invalidate all outstanding links for one contact"
In CiviCRM, edit the contact's hash field (under their record → Edit). Bumping the hash invalidates every checksum issued for that contact. Use as a break-glass for compromised links.