Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,34 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const findCoupon = createAction({
name: 'find_coupon', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Find coupon',
description: 'Find coupon code',
props: {
code: Property.ShortText({
displayName: 'Coupon code',
description: undefined,
required: true,
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const couponInfoResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.GET,
url: `${TALKABLE_API_URL}/coupons/${context.propsValue['code']}`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
},
});
return couponInfoResponse.body;
},
});

View File

@@ -0,0 +1,19 @@
// People
export { findPerson } from './people/find-person';
export { updatePerson } from './people/update-person';
export { anonymizePerson } from './people/anonymize-person';
export { unsubscribePerson } from './people/unsubscribe-person';
// Origins
export { createPurchase } from './origins/create-purchase';
export { createEvent } from './origins/create-event';
export { createEventsBatch } from './origins/create-events-batch';
export { createPurchasesBatch } from './origins/create-purchases-batch';
export { refund } from './origins/refund';
// Loyalty
export { getLoyaltyRedeemActions } from './loyalty/get-loyalty-redeem-actions';
// Referral
export { updateReferralStatus } from './referrals/update-referral-status';
// Coupons
export { findCoupon } from './coupons/find-coupon';
// Rewards
export { claimOffer } from './rewards/claim-offer';

View File

@@ -0,0 +1,34 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const getLoyaltyRedeemActions = createAction({
name: 'get_loyalty_redeem_actions', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Get loyalty actions',
description: 'Get array of loyalty actions',
props: {
person_email: Property.ShortText({
displayName: 'Person email',
description: undefined,
required: true,
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const getLoyaltyRedeemActionsResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.GET,
url: `${TALKABLE_API_URL}/loyalty/members/${context.propsValue['person_email']}/redeem_actions`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
},
});
return getLoyaltyRedeemActionsResponse.body;
},
});

View File

@@ -0,0 +1,147 @@
import {
createAction,
Property,
} from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const createEvent = createAction({
name: 'create_event', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Create event',
description: 'Create event in Talkable',
props: {
email: Property.ShortText({
displayName: 'Email',
description: undefined,
required: true,
}),
event_category: Property.ShortText({
displayName: 'Event category',
description: undefined,
required: true,
}),
event_number: Property.ShortText({
displayName: 'Event number',
description: undefined,
required: true,
}),
subtotal: Property.Number({
displayName: 'Subtotal',
description: undefined,
required: true,
}),
first_name: Property.ShortText({
displayName: 'First name',
description: undefined,
required: false,
}),
last_name: Property.ShortText({
displayName: 'Last name',
description: undefined,
required: false,
}),
username: Property.ShortText({
displayName: 'Username',
description: undefined,
required: false,
}),
customer_id: Property.ShortText({
displayName: 'Customer id',
description: undefined,
required: false,
}),
custom_properties: Property.Object({
displayName: 'Custom properties',
description: undefined,
required: false,
}),
phone_number: Property.ShortText({
displayName: 'Phone number',
description: undefined,
required: false,
}),
campaign_tags: Property.ShortText({
displayName: 'Campaign tags',
description: undefined,
required: false,
}),
sharing_channels: Property.Array({
displayName: 'Sharing channels',
description: undefined,
required: false,
}),
ip_address: Property.ShortText({
displayName: 'IP address',
description: undefined,
required: false,
}),
uuid: Property.ShortText({
displayName: 'UUID',
description: undefined,
required: false,
}),
created_at: Property.ShortText({
displayName: 'Created at',
description: undefined,
required: false,
}),
traffic_source: Property.ShortText({
displayName: 'Traffic source',
description: undefined,
required: false,
}),
coupon_codes: Property.Array({
displayName: 'Coupon codes',
description: undefined,
required: false,
}),
currency_iso_code: Property.ShortText({
displayName: 'Currency iso code',
description: 'Required for multi-currency sites',
required: false,
defaultValue: 'USD',
}),
custom_field: Property.ShortText({
displayName: 'Custom field',
description: undefined,
required: false,
}),
shipping_address: Property.ShortText({
displayName: 'Shipping address',
description: undefined,
required: false,
}),
shipping_zip: Property.ShortText({
displayName: 'Shipping zip',
description: undefined,
required: false,
}),
items: Property.Json({
displayName: 'Items',
description: "You can pass items with event",
required: false,
defaultValue: [
{ price: 10, quantity: 1, product_id: 'SKU1' },
],
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const createEventResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.POST,
url: `${TALKABLE_API_URL}/origins/event`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
data: context.propsValue,
},
});
return createEventResponse.body;
},
});

View File

@@ -0,0 +1,79 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const createEventsBatch = createAction({
name: 'create_events_batch', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Create batch of events',
description: 'Create batch of events in Talkable',
props: {
create_offers: Property.Checkbox({
displayName: 'Create offers',
description: 'Create offers for campaign',
required: false,
defaultValue: false,
}),
events: Property.Json({
displayName: 'Events',
description: undefined,
required: false,
defaultValue: [
{
email: 'user@store.com',
event_category: 'newsletter_subscription',
event_number: '42',
subtotal: 100.44,
first_name: 'John',
last_name: 'Doe',
username: 'johndoe1992',
customer_id: '1024',
custom_properties: {
country: 'US',
eye_color: 'brown',
person_occupation: 'marketing',
},
custom_field: '',
phone_number: '+12025551111',
campaign_tags: 'post-purchase',
sharing_channels: ['facebook', 'custom'],
ip_address: '192.0.2.255',
uuid: '123e4567-e89b-32d1-a456-426614174000',
created_at: '2023-04-27T11:30:42.769-07:00',
traffic_source: 'in-store',
coupon_codes: ['C0001', 'C0002'],
currency_iso_code: 'USD',
shipping_address:
'456 White Finch St., North Augusta, South Carolina, 29860, United States',
shipping_zip: '29860',
items: [
{
price: 100.44,
quantity: 1,
product_id: 'SUBSCRIPTION',
},
],
},
],
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const createEventsBatch = await httpClient
.sendRequest<string[]>({
method: HttpMethod.POST,
url: `${TALKABLE_API_URL}/origins/batch/events`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
data: context.propsValue.events,
create_offers: context.propsValue.create_offers,
},
});
return createEventsBatch.body;
},
});

View File

@@ -0,0 +1,142 @@
import {
createAction,
Property,
} from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const createPurchase = createAction({
name: 'create_purchase', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Create purchase',
description: 'Create purchase in Talkable',
props: {
email: Property.ShortText({
displayName: 'Email',
description: undefined,
required: true,
}),
order_number: Property.ShortText({
displayName: 'Order number',
description: undefined,
required: true,
}),
subtotal: Property.Number({
displayName: 'Subtotal',
description: undefined,
required: true,
}),
first_name: Property.ShortText({
displayName: 'First name',
description: undefined,
required: false,
}),
last_name: Property.ShortText({
displayName: 'Last name',
description: undefined,
required: false,
}),
username: Property.ShortText({
displayName: 'Username',
description: undefined,
required: false,
}),
customer_id: Property.ShortText({
displayName: 'Customer id',
description: undefined,
required: false,
}),
custom_properties: Property.Object({
displayName: 'Custom properties',
description: undefined,
required: false,
}),
phone_number: Property.ShortText({
displayName: 'Phone number',
description: undefined,
required: false,
}),
campaign_tags: Property.ShortText({
displayName: 'Campaign tags',
description: undefined,
required: false,
}),
sharing_channels: Property.Array({
displayName: 'Sharing channels',
description: undefined,
required: false,
}),
ip_address: Property.ShortText({
displayName: 'IP address',
description: undefined,
required: false,
}),
uuid: Property.ShortText({
displayName: 'UUID',
description: undefined,
required: false,
}),
created_at: Property.ShortText({
displayName: 'Created at',
description: undefined,
required: false,
}),
traffic_source: Property.ShortText({
displayName: 'Traffic source',
description: undefined,
required: false,
}),
coupon_codes: Property.Array({
displayName: 'Coupon codes',
description: undefined,
required: false,
}),
currency_iso_code: Property.ShortText({
displayName: 'Currency iso code',
description: 'Required for multi-currency sites',
required: false,
defaultValue: 'USD',
}),
custom_field: Property.ShortText({
displayName: 'Custom field',
description: undefined,
required: false,
}),
shipping_address: Property.ShortText({
displayName: 'Shipping address',
description: undefined,
required: false,
}),
shipping_zip: Property.ShortText({
displayName: 'Shipping zip',
description: undefined,
required: false,
}),
items: Property.Json({
displayName: 'Items',
description: "You can pass items with purchase",
required: false,
defaultValue: [
{ price: 10, quantity: 1, product_id: 'SKU1' },
],
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const createPurchaseResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.POST,
url: `${TALKABLE_API_URL}/origins/purchase`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
data: context.propsValue,
},
});
return createPurchaseResponse.body;
},
});

View File

@@ -0,0 +1,78 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const createPurchasesBatch = createAction({
name: 'create_purchases_batch', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Create batch of purchases',
description: 'Create batch of purchases in Talkable',
props: {
create_offers: Property.Checkbox({
displayName: 'Create offers',
description: 'Create offers for campaign',
required: false,
defaultValue: false,
}),
purchases: Property.Json({
displayName: 'Purchases',
description: undefined,
required: false,
defaultValue: [
{
email: 'customer@store.com',
order_number: '20',
subtotal: 100.44,
first_name: 'John',
last_name: 'Doe',
username: 'johndoe1992',
customer_id: '1024',
custom_properties: {
country: 'US',
eye_color: 'brown',
person_occupation: 'marketing',
},
custom_field: '',
phone_number: '+12025551111',
campaign_tags: 'post-purchase',
sharing_channels: ['facebook', 'custom'],
ip_address: '192.0.2.255',
uuid: '123e4567-e89b-32d1-a456-426614174000',
created_at: '2023-04-27T11:30:42.769-07:00',
traffic_source: 'in-store',
coupon_codes: ['C0001', 'C0002'],
currency_iso_code: 'USD',
shipping_address:
'456 White Finch St., North Augusta, South Carolina, 29860, United States',
shipping_zip: '29860',
items: [
{
price: 25.11,
quantity: 4,
product_id: 'TSHIRT',
},
],
},
],
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const createPurchasesBatch = await httpClient
.sendRequest<string[]>({
method: HttpMethod.POST,
url: `${TALKABLE_API_URL}/origins/batch/purchases`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
data: context.propsValue.purchases,
create_offers: context.propsValue.create_offers,
},
});
return createPurchasesBatch.body;
},
});

View File

@@ -0,0 +1,49 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const refund = createAction({
name: 'refund', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Refund purchase/event',
description: 'Mark origin as refund',
props: {
origin_slug: Property.ShortText({
displayName: 'Order or event number',
description: undefined,
required: true,
}),
refund_subtotal: Property.Number({
displayName: 'Refund subtotal',
description: undefined,
required: false,
}),
refunded_at: Property.DateTime({
displayName: 'Refunded date',
description: undefined,
required: false,
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const { origin_slug, refund_subtotal, refunded_at } = context.propsValue;
const refundResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.POST,
url: `${TALKABLE_API_URL}/origins/${origin_slug}/refund`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
data: {
refunded_at,
refund_subtotal,
},
},
});
return refundResponse.body;
},
});

View File

@@ -0,0 +1,34 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const anonymizePerson = createAction({
name: 'anonymize_person', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Anonymize person',
description: 'Anonymize person by email',
props: {
email: Property.ShortText({
displayName: 'Person email',
description: undefined,
required: true,
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const personAnonymizeResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.POST,
url: `${TALKABLE_API_URL}/people/${context.propsValue['email']}/anonymize`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
},
});
return personAnonymizeResponse.body;
},
});

View File

@@ -0,0 +1,67 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const findPerson = createAction({
name: 'find_person', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Find person',
description: 'Find person by email',
props: {
email: Property.ShortText({
displayName: 'Person email',
description: undefined,
required: true,
}),
scope: Property.StaticDropdown({
displayName: 'Scope',
description: 'Select scope',
required: true,
options: {
options: [
{
label: 'General information',
value: '/',
},
{
label: 'Referrals as advocate',
value: '/referrals_as_advocate',
},
{
label: 'Referrals as friend',
value: '/referrals_as_friend',
},
{
label: 'Rewards',
value: '/rewards',
},
{
label: 'Shares',
value: '/shares_by',
},
{
label: 'Personal data',
value: '/personal_data',
},
],
},
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const personInfoResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.GET,
url: `${TALKABLE_API_URL}/people/${context.propsValue['email']}${context.propsValue['scope']}`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
},
});
return personInfoResponse.body;
},
});

View File

@@ -0,0 +1,34 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const unsubscribePerson = createAction({
name: 'unsubscribe_person', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Unsubscribe person',
description: 'Unsubscribe person by email',
props: {
email: Property.ShortText({
displayName: 'Person email',
description: undefined,
required: true,
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const personUnsubscribeResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.POST,
url: `${TALKABLE_API_URL}/people/${context.propsValue['email']}/unsubscribe`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
},
});
return personUnsubscribeResponse.body;
},
});

View File

@@ -0,0 +1,126 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const updatePerson = createAction({
name: 'update_person', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Update person',
description: 'Update person by email',
props: {
email: Property.ShortText({
displayName: 'Person email',
description: undefined,
required: true,
}),
first_name: Property.ShortText({
displayName: 'Person first name',
description: undefined,
required: false,
}),
last_name: Property.ShortText({
displayName: 'Person last name',
description: undefined,
required: false,
}),
phone_number: Property.ShortText({
displayName: 'Person phone number',
description: undefined,
required: false,
}),
username: Property.ShortText({
displayName: 'Person username',
description: undefined,
required: false,
}),
customer_id: Property.Number({
displayName: 'Customer ID',
description: undefined,
required: false,
}),
custom_properties: Property.Object({
displayName: 'Custom properties',
description: undefined,
required: false,
}),
gated_param_subscribed: Property.StaticDropdown({
displayName: 'Opt-in status',
description: 'Opt-in status true/false',
required: false,
options: {
options: [
{
label: 'true',
value: 'true',
},
{
label: 'false',
value: 'false',
},
],
},
}),
unsubscribed: Property.StaticDropdown({
displayName: 'Unsubscribe status',
description: 'Unsubscribe status true/false',
required: false,
options: {
options: [
{
label: 'true',
value: 'true',
},
{
label: 'false',
value: 'false',
},
],
},
}),
unsubscribed_at: Property.DateTime({
displayName: 'Unsubscribed date',
description: undefined,
required: false,
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const {
email,
first_name,
last_name,
phone_number,
username,
customer_id,
custom_properties,
gated_param_subscribed,
unsubscribed,
unsubscribed_at,
} = context.propsValue;
const personUpdateResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.PUT,
url: `${TALKABLE_API_URL}/people/${email}`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
data: {
first_name,
last_name,
phone_number,
username,
customer_id,
custom_properties,
gated_param_subscribed,
unsubscribed,
unsubscribed_at,
},
},
});
return personUpdateResponse.body;
},
});

View File

@@ -0,0 +1,47 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const updateReferralStatus = createAction({
name: 'update-referral-status', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Update referral status',
description: 'You can void or approve referral',
props: {
origin_slug: Property.ShortText({
displayName: 'Order or event number',
description: undefined,
required: true,
}),
status: Property.StaticDropdown({
displayName: 'Status',
description:
'Select referral status. Only "approved" or "voided" are accepted',
required: true,
options: {
options: [
{ label: 'approved', value: 'approved' },
{ label: 'voided', value: 'voided' },
],
},
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const updateReferralStatusResponse = await httpClient
.sendRequest<string[]>({
method: HttpMethod.POST,
url: `${TALKABLE_API_URL}/origins/${context.propsValue['origin_slug']}/referral`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
status: context.propsValue['status'], // we have only one status so it's hardcoded
},
});
return updateReferralStatusResponse.body;
},
});

View File

@@ -0,0 +1,47 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { talkableAuth } from '../../..';
export const claimOffer = createAction({
name: 'claim-offer', // Must be a unique across the piece, this shouldn't be changed.
auth: talkableAuth,
displayName: 'Share and claim offer',
description: "Using this action, you can share and get a friend's reward",
props: {
advocate_email: Property.ShortText({
displayName: 'Advocate email',
description: "Example : advocate@example.com",
required: true,
}),
friend_email: Property.ShortText({
displayName: 'Friend email',
description: "Example : friend@example.com",
required: true,
}),
campaign_tag: Property.ShortText({
displayName: 'Campaign tag',
description: "Example : invite",
required: true,
}),
},
async run(context) {
const TALKABLE_API_URL = 'https://www.talkable.com/api/v2';
const { site, api_key } = context.auth.props;
const claimOffer = await httpClient
.sendRequest<string[]>({
method: HttpMethod.POST,
url: `${TALKABLE_API_URL}/offer_claims`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body: {
site_slug: site,
advocate_email: context.propsValue['advocate_email'],
friend_email: context.propsValue['friend_email'],
campaign_tag: context.propsValue['campaign_tag'],
},
});
return claimOffer.body;
},
});