> ## Documentation Index
> Fetch the complete documentation index at: https://docs.snappy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Triggered Gifts

> Create gifts programmatically with POST /v2/gifts. Covers bulk chunking, idempotency keys, and partial failure handling.

The Send Triggered Gifts pattern creates gifts programmatically for a list of recipients — triggered by events like hiring, anniversaries, or milestone completions. Gifts are created in the V2 gifting API and sent to recipients who choose their own product from a curated selection.

## Prerequisites

* API key with `gifts:create` scope
* At least one Campaign configured in the Snappy Dashboard
* The `campaignId` for the campaign you're sending under

## Create gifts

```text theme={null}
POST /v2/gifts
```

Pass a `recipients` array — each entry creates one gift for one recipient.

<Note>
  The V2 gifting API uses lowercase field names for recipient names: `firstname` and `lastname` (not camelCase). This differs from the V3 orders API which uses `firstName` / `lastName`.
</Note>

```javascript JavaScript theme={null}
async function sendGifts(campaignId, recipients) {
  const res = await fetch(`${SNAPPY}/v2/gifts`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      campaignId,
      recipients: recipients.map(r => ({
        firstname: r.firstName,      // lowercase - V2 API
        lastname: r.lastName,        // lowercase - V2 API
        email: r.email,
        key: r.stableId,             // idempotency key - see below
      })),
    }),
  });
  return res.json();
}
```

```python Python theme={null}
def send_gifts(campaign_id, recipients):
    payload = {
        "campaignId": campaign_id,
        "recipients": [
            {
                "firstname": r["first_name"],   # lowercase - V2 API
                "lastname": r["last_name"],      # lowercase - V2 API
                "email": r["email"],
                "key": r["stable_id"],           # idempotency key - see below
            }
            for r in recipients
        ],
    }
    res = requests.post(f"{SNAPPY}/v2/gifts", headers=headers, json=payload)
    return res.json()
```

## Chunking for large lists

The V2 gifts endpoint has a maximum recipients per request. For large sends, split into chunks of 100 and send sequentially (or with bounded concurrency):

```javascript JavaScript theme={null}
const CHUNK_SIZE = 100;

async function sendGiftsInBulk(campaignId, allRecipients) {
  const results = [];
  for (let i = 0; i < allRecipients.length; i += CHUNK_SIZE) {
    const chunk = allRecipients.slice(i, i + CHUNK_SIZE);
    const result = await sendGifts(campaignId, chunk);
    results.push(result);
  }
  return results;
}
```

```python Python theme={null}
CHUNK_SIZE = 100

def send_gifts_in_bulk(campaign_id, all_recipients):
    results = []
    for i in range(0, len(all_recipients), CHUNK_SIZE):
        chunk = all_recipients[i:i + CHUNK_SIZE]
        result = send_gifts(campaign_id, chunk)
        results.append(result)
    return results
```

## Idempotency

Each recipient entry accepts a `key` field — a stable, permanent identifier for that recipient in the context of this send. Snappy uses this to deduplicate: sending the same `key` twice does not create a second gift.

**Derive `key` from stable identifiers**, not from random values or timestamps:

```javascript JavaScript theme={null}
// Good: stable per recipient per campaign
const key = `campaign-${campaignId}-user-${userId}`;

// Bad: changes on retry, defeats idempotency
const key = `${Date.now()}-${Math.random()}`;
```

Keys must be unique within a campaign. A good pattern: `{campaignId}-{userId}` or `{campaignId}-{employeeId}`.

## Partial failure handling

The API may succeed for some recipients and fail for others in the same request. The response includes per-recipient status:

| Outcome            | Field                               | Action                                  |
| :----------------- | :---------------------------------- | :-------------------------------------- |
| Success            | `gifts[].id` present                | Record gift ID for tracking             |
| Validation error   | `errors[].recipient`                | Fix the data and re-send that recipient |
| Duplicate key      | `errors[].code === "duplicate_key"` | Already sent — no action needed         |
| Campaign not found | Top-level `404`                     | Check `campaignId`                      |

Always inspect `errors` in the response and re-run the failed recipients rather than the full list. Since each recipient has a `key`, re-running is safe — duplicates are silently ignored.

```javascript JavaScript theme={null}
function extractFailures(result) {
  return (result.errors ?? []).filter(e => e.code !== "duplicate_key");
}
```

## Related

* [Auto-claim Gifts as Fallback](/patterns/auto-claim-gifts-as-fallback) — what to do when a recipient doesn't claim their gift
* [Track Order Fulfillment](/patterns/track-order-fulfillment) — subscribe to webhooks once the gift is claimed
