Files
WebForm-mw/EMAIL_DELIVERY.md

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:

  1. A CiviCRM message template that bakes the URL into an email body.
  2. A way to trigger sending (one of three options below).
  3. 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:

  1. Navigate to Mailings → Message Templates → Add Message Template.

  2. Title: Co-op Check-in invitation

  3. Subject:

    Time for your co-op check-in
    
  4. 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
    
  5. 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}&amp;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>
    
  6. Save.

Token reference:

  • {contact.display_name} — the form-filler's name
  • {contact.contact_id} — their CiviCRM ID (becomes cid)
  • {contact.checksum} — a fresh secure token (becomes cs)

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.

  1. Open the organization's contact record in CiviCRM.
  2. Relationships tab → find the row with "Primary Contact" → click the related individual's name.
  3. On the individual's contact view: Actions → Send Email.
  4. Use Template: select Co-op Check-in invitation. The subject and body populate; tokens render in the preview.
  5. 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).

  1. Build a Smart Group of contacts whose Primary Contact relationships point at orgs in active stages. Example query:

    • ContactsAdvanced Search
    • Add criteria: Has relationshipPrimary Contact → status: Active
    • Optionally constrain by the related org's Food_Co_op_Organizing.Stage field
    • Save as Smart Group: "Active co-op primary contacts"
  2. Mailings → New Mailing.

  3. Recipients: the smart group above.

  4. Choose template: Co-op Check-in invitation.

  5. 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:

  1. Administer → CiviRules → Manage Rules → New Rule.
  2. Title: Send Co-op Check-in invitation.
  3. 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).
  4. Action: Send email to contact via message template.
    • Template: Co-op Check-in invitation.
    • Recipient: Contact in relationship → "Primary Contact" → side B (the Individual).
  5. 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.


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 set CIVI_BASE_URL etc.

Operational runbook

"We need to send invitations for this month"

  1. (Bulk path) Use Option B: build/refresh the Smart Group, schedule the CiviMail mailing, send.
  2. (Per-org path) Use Option A: open each org → Send Email to Primary Contact.
  3. (Auto path) Use Option C: just advance stages; emails go out automatically.
  1. Hit /api/preview-link?cid=<their-cid>&token=<HEALTH_TOKEN>.
  2. Verify the response shows the right org name + stage.
  3. Send the URL from the response directly to the contact (or have them try it).
  4. If /api/preview-link 404s, the contact lacks a Primary Contact relationship — fix in CiviCRM first.
  5. If their old URL truly expired (older than 14 days / your cs_offset setting), CiviCRM rejects it. The new one from preview-link will work.

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.