Skip to content

Email Delivery API

Use a project private token from your backend to send published email templates. Never expose this token in browser or mobile code. Follow the email sending policy before sending to your audience.

Node.js

ts
import { createEmailsClient } from '@funnelsgrove/emails';

const emails = createEmailsClient({
  privateToken: process.env.FUNNELSGROVE_PRIVATE_TOKEN!,
});

const receipt = await emails.send({
  template: 'login-code',
  to: 'delivered@example.com',
  variables: { code: '481953', expiresInMinutes: 10 },
});

console.log(receipt);

Project CLI

Manage project email files with the existing agentic CLI:

bash
fgrove email pull --workspace "$WORKSPACE" --project "$PROJECT"
fgrove email validate
fgrove email push --workspace "$WORKSPACE" --project "$PROJECT"

Create, publish, and enable a project sequence with the same CLI. Choose one --funnel-id, or use --all-funnels explicitly:

bash
fgrove email sequence create welcome-series \
  --name "Welcome series" \
  --template welcome \
  --key welcome \
  --funnel-id "$FUNNEL_ID" \
  --delay-hours 0

fgrove email sequence add-step welcome-series \
  --template reminder \
  --key reminder \
  --delay-days 2

fgrove email validate
fgrove email push --workspace "$WORKSPACE" --project "$PROJECT"
fgrove email sequence publish welcome-series --workspace "$WORKSPACE" --project "$PROJECT"
fgrove email sequence enable welcome-series --workspace "$WORKSPACE" --project "$PROJECT"

Use fgrove email sequence disable welcome-series to stop new enrollments. Sequence activation is explicit and requires a published version. The project's verified sending domain must also have a valid DMARC policy; subdomains can inherit the policy published on their parent domain.

Disabling a sequence only stops new enrollments. To idempotently cancel the active enrollment for one existing funnel user, use the sequence slug stored in the local emails/sequences directory:

bash
export FUNNELSGROVE_PRIVATE_TOKEN="fg_private_..."

fgrove email sequence cancel welcome-series \
  --user-id "$FUNNEL_END_USER_ID" \
  --reason customer_request

The first successful call reports the number cancelled. A safe retry returns cancelled: 0 when there is no longer an active enrollment. See the Sequences API for the Node.js and HTTP forms of the same operation.

Send a published template through the production delivery path with a private project token:

bash
export FUNNELSGROVE_PRIVATE_TOKEN="fg_private_..."

fgrove email send \
  --template luvia-e2e-email \
  --to delivered@example.com \
  --variables '{"firstName":"Andrew","message":"Your Luvia email delivery is working."}'

The command waits for a terminal delivery status by default. Use --no-wait to return as soon as the email is queued, and --idempotency-key when retrying the same logical send.

Retrieve a delivery

ts
const status = await emails.get(receipt.id);
console.log(status.status);

The API uses Authorization: Bearer <private token>, accepts an optional idempotencyKey, and returns a queued delivery receipt. The template has to be published in the project and a verified default sending domain must be active.

Integration endpoints

All requests use Authorization: Bearer $FUNNELSGROVE_PRIVATE_TOKEN.

MethodEndpointNode.js SDKPurpose
POST/integration/v1/email/deliveriesemails.send(input)Queue a published template for delivery. Requires an Idempotency-Key header.
GET/integration/v1/email/deliveries/:deliveryIdemails.get(deliveryId)Retrieve the current delivery status.
PATCH/integration/v1/email/deliveries/:deliveryId/variablesemails.updateVariables(deliveryId, variables)Replace validated variables during the pre-send personalization window.

For a personalized lifecycle step, FunnelsGrove emits email.personalization_requested 20 minutes before its due time. Update variables after receiving that webhook and before the delivery deadline:

ts
const personalized = await emails.updateVariables(event.data.deliveryId, {
  firstName: customer.firstName,
  recommendation: customer.recommendation,
});

console.log(personalized.dueAt);
bash
curl --request PATCH \
  "https://sdk-api.funnelsgrove.com/integration/v1/email/deliveries/$DELIVERY_ID/variables" \
  --header "Authorization: Bearer $FUNNELSGROVE_PRIVATE_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"variables":{"firstName":"Ada"}}'

Only declared template variables with valid types are accepted. The endpoint rejects requests outside the final personalization window or after sending has started.