Files
WebForm-mw/EMAIL_DELIVERY.md

208 lines
7.3 KiB
Markdown

# 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):
```html
<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:
- **Contacts** → **Advanced Search**
- Add criteria: `Has relationship` → `Primary 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.
---
## 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:**
```bash
curl "https://check-in.fci.coop/api/preview-link?cid=513&token=$HEALTH_TOKEN"
```
**Response:**
```json
{
"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.
### "Contact reports the link doesn't work"
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.
### "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.