103 lines
3.3 KiB
Plaintext
103 lines
3.3 KiB
Plaintext
---
|
|
title: Double opt-in
|
|
description: Require a confirmation click before a new signup starts receiving marketing email
|
|
icon: MailCheck
|
|
---
|
|
|
|
Double opt-in adds a confirmation step between "user signs up" and "user starts getting marketing email." It's the standard way to avoid mailing typoed addresses, role accounts, and anyone who didn't actually consent.
|
|
|
|
The trick is `{{subscribeUrl}}`: a per-contact link Plunk auto-injects into every send. Clicking it flips `subscribed` to `true` and fires a `contact.subscribed` event.
|
|
|
|
## Setup
|
|
|
|
import {Step, Steps} from 'fumadocs-ui/components/steps';
|
|
|
|
<Steps>
|
|
|
|
<Step>
|
|
|
|
### Create two templates
|
|
|
|
- A **Transactional** template for the confirmation email, containing `{{subscribeUrl}}`:
|
|
|
|
```html
|
|
<p>Hi {{firstName}}, please confirm your email to start receiving updates:</p>
|
|
<p><a href="{{subscribeUrl}}">Confirm my email</a></p>
|
|
```
|
|
|
|
- A **Marketing** template for the welcome email that goes out *after* they confirm.
|
|
|
|
<Callout title="The confirmation must be transactional" type="warn">
|
|
A marketing template targeted at an unsubscribed contact is [silently skipped](/concepts/contacts#emails-by-subscription-state). Use a transactional template for the confirmation specifically — it bypasses the subscription check.
|
|
</Callout>
|
|
|
|
</Step>
|
|
|
|
<Step>
|
|
|
|
### Trigger the signup from your backend
|
|
|
|
Two calls with your secret key (`sk_*`): create the contact unsubscribed, then track the event that fires the confirmation workflow.
|
|
|
|
```bash
|
|
curl https://next-api.useplunk.com/contacts \
|
|
-H "Authorization: Bearer sk_your_secret_key" \
|
|
-d '{ "email": "[email protected]", "subscribed": false, "data": { "firstName": "Ada" } }'
|
|
|
|
curl https://next-api.useplunk.com/v1/track \
|
|
-H "Authorization: Bearer sk_your_secret_key" \
|
|
-d '{ "event": "signup.pending", "email": "[email protected]", "subscribed": false }'
|
|
```
|
|
|
|
Both calls pass `subscribed: false`. If you skip the first call and rely on `/v1/track` alone, tracking on an unknown email creates the contact — but defaults it to subscribed, which defeats the point.
|
|
|
|
</Step>
|
|
|
|
<Step>
|
|
|
|
### Workflow A: send the confirmation
|
|
|
|
**Workflows → New workflow**:
|
|
|
|
- **Trigger**: `EVENT` on `signup.pending`
|
|
- `SEND_EMAIL` step → transactional confirmation template
|
|
|
|
Enable it.
|
|
|
|
</Step>
|
|
|
|
<Step>
|
|
|
|
### Workflow B: welcome them after confirmation
|
|
|
|
**Workflows → New workflow**:
|
|
|
|
- **Trigger**: `EVENT` on `contact.subscribed`
|
|
- `SEND_EMAIL` step → marketing welcome template
|
|
|
|
Enable it. `contact.subscribed` fires whenever a contact opts in — including via `{{subscribeUrl}}`, the preferences page, or the API — so this workflow handles both first-time confirmations and resubscribes.
|
|
|
|
</Step>
|
|
|
|
</Steps>
|
|
|
|
## Reminder if they don't confirm
|
|
|
|
Extend Workflow A with a `WAIT_FOR_EVENT` step after the send:
|
|
|
|
- **Event**: `contact.subscribed`
|
|
- **Timeout**: `86400` (24 hours)
|
|
|
|
On timeout, send a single reminder (also transactional). Keep the number of reminders small — repeated confirmation prompts look like spam to mailbox providers as much as to recipients.
|
|
|
|
## What's next
|
|
|
|
<Cards>
|
|
<Card title="Unsubscribe & preferences pages" href="/guides/unsubscribe-pages">
|
|
Detail on `{{subscribeUrl}}` and the hosted pages.
|
|
</Card>
|
|
<Card title="Templates" href="/concepts/templates">
|
|
The difference between Marketing, Transactional, and Headless templates.
|
|
</Card>
|
|
</Cards>
|