Confirm email subscriptions

Request email subscription confirmation, wait for recipient consent, and add confirmed contacts to a contact group.

Use double opt-in to add someone to a contact group only after they confirm their email address. Your server requests a confirmation email; the recipient follows its link and chooses Confirm subscription. Opening the link alone does not subscribe them.

Request confirmation

Create a contact group for your audience and use a sender on your verified sending domain. Keep your Samva API key on your server. Apply your own form abuse controls before calling Samva.

subscribe.ts
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });

const result = await samva.subscriptions.subscribe({
  email: "ada@example.com",
  contactGroupId: "cgrp_1234567890abcdef",
  from: { email: "newsletter@your-domain.com", name: "Your newsletter" },
  consentSource: "newsletter-signup",
});

console.log(result.success, result.expiresAt);
curl -X POST https://api.samva.dev/v1/subscriptions \
  -H "x-api-key: $SAMVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"ada@example.com","contactGroupId":"cgrp_1234567890abcdef","from":{"email":"newsletter@your-domain.com","name":"Your newsletter"},"consentSource":"newsletter-signup"}'

A successful response accepts the confirmation request. It does not establish consent or guarantee delivery. The response contains success: true and expiresAt; it never returns the confirmation link. Message content and previews show a placeholder; only the recipient's email contains the working confirmation link. See Request subscription confirmation for request fields and errors.

consentSource records where signup happened. If you supply consentIp, derive it on your server from the incoming request and your trusted proxy configuration. Both fields are caller-supplied context, not proof Samva independently verified. Do not accept arbitrary browser values as trusted consent evidence.

Wait for recipient confirmation

The confirmation link expires after seven days and can be used once. The email links to Samva's hosted confirmation page. Treat that link as private: do not put it in analytics, logs, support tickets, or shared previews. The recipient must choose Confirm subscription to complete the request.

Successful confirmation adds the contact to the requested active, static contact group and emits contact.subscribed. Use that event to start welcome flows. Each successfully confirmed fresh link emits an event, including when the contact already belongs to the group. Creating a request alone does not add membership or clear an opt-out.

If you set unsubscribeGroupId, successful confirmation clears that address's opt-out for that unsubscribe group. It does not change other unsubscribe groups or remove delivery suppressions. A contact group selects an audience; an unsubscribe group records which mail a recipient declines. Keep both models when sending campaigns.

Handle failures and retries

An invalid, expired, or already-used link cannot confirm a subscription. Ask the recipient to request another email when their link expires. Each accepted request creates a fresh link; retrying the signup request can send another confirmation email, so do not automatically repeat it after an uncertain response. Apply a resend delay in your own form.

If the same confirmation email is delivered again, either delivered link can confirm the request. Confirming one makes all links for that request unusable.

Requesting confirmation can create an active contact for delivery, without subscribing it to the contact group. New contacts count toward your plan's active-contact limit. If that limit is full, the signup request returns HTTP 402 without sending a confirmation email.

If a contact is deleted before confirmation, confirming the link checks capacity before recreating it. A full list leaves the link unused and makes no membership, opt-out, or webhook changes. The hosted page asks the recipient to notify the sender and retry the same link before it expires. Existing contacts can confirm without an additional capacity check.

If confirmation fails temporarily, the recipient can retry the same link. Membership changes and confirmation complete together; a failed confirmation does not establish consent. Handle duplicate webhook deliveries using the webhook delivery contract.

Customize the email

The confirmation email uses the brand and footer configured for your sender and sends from the from address you supplied. Set templateSlug to use a published template that accepts confirmUrl as input. The rendered HTML must contain at least one link using the complete, unchanged value as an <a href>:

<a href={confirmUrl}>Confirm subscription</a>

HTML-only templates are supported. If the template includes a text part, it must also include the complete confirmUrl. Put the complete URL on its own line or separate it from surrounding text with whitespace. A parenthesized URL, as in Confirm (URL), is also supported. Do not append parameters or wrap it in another URL. Do not use confirmUrl in images, srcset, styles, other attributes, the subject, or HTML body text. Samva rejects the subscription request with a validation error naming the template if either required link is missing or the template uses confirmUrl in any of those places.

Keep the request's purpose clear in the subject and body so recipients know which subscription they are confirming.

A custom template's unsubscribe-group setting does not block the confirmation email. This lets a recipient confirm a request to rejoin a group they previously opted out of. Delivery suppressions still apply.

CLI and MCP

Request confirmation from your terminal:

samva subscriptions subscribe \
  --email ada@example.com \
  --contact-group-id cgrp_1234567890abcdef \
  --from newsletter@your-domain.com \
  --from-name "Your newsletter" \
  --consent-source newsletter-signup \
  --json

An authenticated MCP agent can request the same email with contacts_subscribe. Both surfaces start the request; the recipient completes confirmation on the hosted page.

Related documentation

On this page